Безопасность

В основе Hermes Agent лежит модель безопасности по принципу эшелонированной защиты. На этой странице разобраны все границы безопасности: от подтверждения команд до изоляции в контейнерах и авторизации пользователей в мессенджерах.

Обзор

Модель безопасности состоит из семи слоёв:

  1. Авторизация пользователей — кто вообще может писать агенту (списки разрешений, парное подключение через DM)
  2. Подтверждение опасных команд — человек в контуре, когда речь о разрушительных операциях
  3. Изоляция в контейнерах — песочница на Docker/Singularity/Modal с усиленными настройками
  4. Фильтрация учётных данных для MCP — изоляция переменных окружения для подпроцессов MCP
  5. Сканирование контекстных файлов — поиск prompt injection в файлах проекта
  6. Изоляция между сессиями — сессии не видят данные и состояние друг друга; пути хранения для cron-задач защищены от атак с обходом каталога (path traversal)
  7. Санитизация входных данных — параметры рабочего каталога в бэкендах терминала сверяются со списком разрешений, чтобы исключить инъекцию в шелл

Подтверждение опасных команд

Перед запуском любой команды Hermes сверяет её с подобранным списком опасных шаблонов. Совпало — пользователь должен явно подтвердить выполнение.

Режимы подтверждения

Система подтверждения работает в трёх режимах; режим задаётся ключом approvals.mode в ~/.hermes/config.yaml:

approvals:
  mode: manual                    # manual | smart | off
  timeout: 60                     # сколько секунд ждать ответа пользователя (по умолчанию: 60)
  cron_mode: deny                 # deny | approve — что делают cron-задачи при опасной команде
  mcp_reload_confirm: true        # /reload-mcp спрашивает перед сбросом кэша инструментов MCP
  destructive_slash_confirm: true # /clear, /new, /reset, /undo спрашивают перед сбросом состояния

Полный набор ключей:

КлючПо умолчаниюЗа что отвечает
modemanualПолитика подтверждения опасных shell-команд — см. таблицу ниже.
timeout60Сколько секунд Hermes ждёт ответа на запрос подтверждения, прежде чем сработает тайм-аут.
cron_modedenyКак cron-задачи ведут себя в headless-режиме, когда натыкаются на запрос подтверждения опасной команды. deny блокирует команду (агент должен искать другой путь); approve автоматически одобряет всё в контексте cron.
mcp_reload_confirmtrueЕсли включено, /reload-mcp спрашивает перед пересборкой набора инструментов MCP. Пересборка сбрасывает кэш промпта у провайдера (схемы инструментов лежат в системном промпте), поэтому следующее сообщение заново отправит полный объём входных токенов. Те, кто нажимает Always Approve, переводят этот ключ в false.
destructive_slash_confirmtrueЕсли включено, разрушительные slash-команды сессии (/clear, /new, /reset, /undo) спрашивают перед тем, как сбросить состояние диалога. Диалог из трёх вариантов (Approve Once / Always Approve / Cancel) выводится через нативные кнопки да/нет в Telegram, Discord и Slack; в остальных местах — текстовый fallback. Те, кто нажимает Always Approve, переводят этот ключ в false. В TUI используется собственное модальное окно (отключить его можно через HERMES_TUI_NO_CONFIRM=1).
РежимПоведение
manual (по умолчанию)Всегда спрашивать подтверждение на опасные команды
smartОценивает риск через вспомогательную LLM. Безопасные команды (например, python -c "print('hello')") одобряются автоматически. По-настоящему опасные — автоматически отклоняются. Спорные случаи уходят на ручное подтверждение.
offОтключить все проверки подтверждения — равнозначно запуску с --yolo. Все команды выполняются без запросов.
внимание

Значение approvals.mode: off отключает все защитные запросы. Используйте только в доверенных средах (CI/CD, контейнеры и т. п.).

Режим YOLO

Режим YOLO обходит все запросы на подтверждение опасных команд в текущей сессии. Включить его можно тремя способами:

  1. Флаг CLI: запустите сессию через hermes --yolo или hermes chat --yolo
  2. Slash-команда: введите /yolo во время сессии, чтобы включить или выключить режим
  3. Переменная окружения: установите HERMES_YOLO_MODE=1

Команда /yolo работает как переключатель — каждый вызов меняет состояние на противоположное:

> /yolo
  ⚡ YOLO mode ON — all commands auto-approved. Use with caution.

> /yolo
  ⚠ YOLO mode OFF — dangerous commands will require approval.

Режим YOLO доступен и в CLI, и в сессиях шлюза. Внутри он выставляет переменную окружения HERMES_YOLO_MODE, которая проверяется перед каждым запуском команды.

Пока YOLO активен, Hermes держит на виду два постоянных напоминания — забыть про обход подтверждений не получится:

  • Красная строка-баннер при старте сессии, если YOLO уже включён: ⚠ YOLO mode — all approval prompts bypassed. Когда YOLO выключен, баннер скрыт, чтобы не засорять стандартное приветствие.
  • Фрагмент ⚠ YOLO в строке состояния на любой ширине экрана; он обновляется на лету, как только вы переключаете YOLO (и в renderer с форматированием, и в текстовом fallback).
опасно

Режим YOLO отключает все проверки безопасности опасных команд на время сессии — кроме жёсткого блок-листа (см. ниже). Включайте его только когда полностью доверяете генерируемым командам (например, хорошо проверенным скриптам автоматизации в одноразовых средах).

Для разрушительных slash-команд сессии (/clear, /new / /reset, /undo, /quit --delete/exit --delete это псевдоним) CLI тоже спрашивает подтверждение перед запуском. См. Slash-команды — Запросы подтверждения для разрушительных команд.

Жёсткий блок-лист (всегда включённый минимум)

Некоторые команды настолько катастрофичны — необратимые затирания файловой системы, fork-бомбы, прямая запись на блочное устройство, — что Hermes отказывается их выполнять независимо от:

  • включённого --yolo / /yolo
  • approvals.mode: off
  • cron-задач в headless-режиме approve
  • явного нажатия пользователем «allow always»

Блок-лист — это фундамент под --yolo. Он срабатывает раньше, чем команда вообще доходит до слоя подтверждения, и флага для его отключения нет. Шаблоны, которые сейчас покрыты (список неполный; синхронизируется с tools/approval.py::UNRECOVERABLE_BLOCKLIST):

ШаблонПочему он жёсткий
rm -rf / и очевидные вариацииЗатирает корень файловой системы
rm -rf --no-preserve-root /Явный вариант «да, я именно про корень»
:(){ :|:& };: (bash fork bomb)Загружает хост до перезагрузки
mkfs.* на смонтированном корневом устройствеФорматирует работающую систему
dd if=/dev/zero of=/dev/sd*Обнуляет физический диск
Передача недоверенных URL в sh на верхнем уровне rootfsВектор удалённого выполнения кода, слишком широкий, чтобы его одобрять

Если вы упёрлись в блок-лист, вызов инструмента возвращает агенту пояснительную ошибку, и ничего не запускается. А если такая команда нужна в легитимном сценарии (скажем, вы оператор pipeline, который сам стирает и переустанавливает систему) — запускайте её вне агента.

Тайм-аут подтверждения

Когда появляется запрос на опасную команду, у пользователя есть настраиваемое время на ответ. Если за отведённый срок ответа нет, команда по умолчанию отклоняется (fail-closed, отказ в безопасную сторону).

Тайм-аут настраивается в ~/.hermes/config.yaml:

approvals:
  timeout: 60  # секунды (по умолчанию: 60)

Что вызывает подтверждение

Запрос подтверждения вызывают следующие шаблоны (заданы в tools/approval.py):

ШаблонОписание
rm -r / rm --recursiveРекурсивное удаление
rm ... /Удаление в корневом пути
chmod 777/666 / o+w / a+wПрава на запись для всех/прочих
chmod --recursive с небезопасными правамиРекурсивная запись для всех/прочих (длинный флаг)
chown -R root / chown --recursive rootРекурсивный chown на root
mkfsФорматирование файловой системы
dd if=Копирование диска
> /dev/sdЗапись на блочное устройство
DROP TABLE/DATABASESQL DROP
DELETE FROM (без WHERE)SQL DELETE без WHERE
TRUNCATE TABLESQL TRUNCATE
> /etc/Перезапись системного конфига
systemctl stop/restart/disable/maskОстановка/перезапуск/отключение системных служб
kill -9 -1Завершение всех процессов
pkill -9Принудительное завершение процессов
Шаблоны fork-бомбFork-бомбы
bash -c / sh -c / zsh -c / ksh -cВыполнение команды через флаг -c (включая комбинированные флаги вроде -lc)
python -e / perl -e / ruby -e / node -cВыполнение скрипта через флаг -e/-c
curl ... | sh / wget ... | shПередача удалённого содержимого в шелл
bash <(curl ...) / sh <(wget ...)Выполнение удалённого скрипта через подстановку процесса
tee в /etc/, ~/.ssh/, ~/.hermes/.envПерезапись чувствительного файла через tee
> / >> в /etc/, ~/.ssh/, ~/.hermes/.envПерезапись чувствительного файла через перенаправление
xargs rmxargs вместе с rm
find -exec rm / find -deletefind с разрушительными действиями
cp/mv/install в /etc/Копирование/перемещение файла в системный конфиг
sed -i / sed --in-place на /etc/Правка системного конфига на месте
pkill/killall hermes/gatewayЗащита от самозавершения
gateway run с &/disown/nohup/setsidНе даёт запустить шлюз в обход менеджера служб
инфо

Обход в контейнере: в бэкендах docker, singularity, modal и daytona проверки опасных команд пропускаются, потому что границей безопасности служит сам контейнер. Разрушительные команды внутри контейнера не навредят хосту.

Поток подтверждения (CLI)

В интерактивном CLI опасные команды показывают встроенный запрос подтверждения:

  ⚠️  DANGEROUS COMMAND: recursive delete
      rm -rf /tmp/old-project

      [o]nce  |  [s]ession  |  [a]lways  |  [d]eny

      Choice [o/s/a/D]:

Четыре варианта:

  • once — разрешить только этот единичный запуск
  • session — разрешить этот шаблон до конца сессии
  • always — добавить в постоянный список разрешений (сохраняется в config.yaml)
  • deny (по умолчанию) — заблокировать команду

Поток подтверждения (шлюз/мессенджеры)

В мессенджерах агент отправляет в чат детали опасной команды и ждёт ответа пользователя:

  • Ответьте yes, y, approve, ok или go, чтобы одобрить
  • Ответьте no, n, deny или cancel, чтобы отклонить

Переменная окружения HERMES_EXEC_ASK=1 выставляется автоматически при запуске шлюза.

Постоянный список разрешений

Команды, одобренные через «always», сохраняются в ~/.hermes/config.yaml:

# Постоянно разрешённые шаблоны опасных команд
command_allowlist:
  - rm
  - systemctl

Эти шаблоны загружаются при старте и молча одобряются во всех последующих сессиях.

совет

Командой hermes config edit можно просмотреть или убрать шаблоны из постоянного списка разрешений.

Авторизация пользователей (шлюз)

При работе шлюза сообщений Hermes управляет тем, кто может взаимодействовать с ботом, через многослойную систему авторизации.

Порядок проверки авторизации

Метод _is_user_authorized() проверяет в таком порядке:

  1. Флаг «разрешить всех» для платформы (например, DISCORD_ALLOW_ALL_USERS=true)
  2. Список одобренных через парное подключение DM (пользователи, одобренные по коду подключения)
  3. Списки разрешений конкретной платформы (например, TELEGRAM_ALLOWED_USERS=12345,67890)
  4. Глобальный список разрешений (GATEWAY_ALLOWED_USERS=12345,67890)
  5. Глобальный «разрешить всех» (GATEWAY_ALLOW_ALL_USERS=true)
  6. По умолчанию: отказ

Списки разрешений по платформам

Задайте разрешённые ID пользователей через запятую в ~/.hermes/.env:

# Списки разрешений для конкретных платформ
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=111222333444555666
WHATSAPP_ALLOWED_USERS=15551234567
SLACK_ALLOWED_USERS=U01ABC123

# Кросс-платформенный список (проверяется для всех платформ)
GATEWAY_ALLOWED_USERS=123456789

# «Разрешить всех» для платформы (используйте с осторожностью)
DISCORD_ALLOW_ALL_USERS=true

# Глобальный «разрешить всех» (используйте с особой осторожностью)
GATEWAY_ALLOW_ALL_USERS=true
внимание

Если не настроен ни один список разрешений и не задан GATEWAY_ALLOW_ALL_USERS, всем пользователям будет отказано. При старте шлюз пишет предупреждение в лог:

No user allowlists configured. All unauthorized users will be denied.
Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access,
or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).

Система парного подключения через DM

Для более гибкой авторизации в Hermes встроена система подключения по коду. Вместо того чтобы заранее перечислять ID, незнакомый пользователь получает одноразовый код подключения, который владелец бота одобряет из CLI.

Как это работает:

  1. Незнакомый пользователь пишет боту в личные сообщения
  2. Бот отвечает 8-символьным кодом подключения
  3. Владелец бота выполняет в CLI hermes pairing approve <platform> <code>
  4. Пользователь получает постоянное одобрение для этой платформы

Поведение для неавторизованных личных сообщений настраивается в ~/.hermes/config.yaml:

unauthorized_dm_behavior: pair

whatsapp:
  unauthorized_dm_behavior: ignore
  • pair — значение по умолчанию. На неавторизованный DM приходит ответ с кодом подключения.
  • ignore — неавторизованные DM молча отбрасываются.
  • Секции платформ переопределяют глобальное значение, так что можно держать подключение по коду в Telegram, а WhatsApp оставить в тишине.

Меры безопасности (по рекомендациям OWASP и NIST SP 800-63-4):

МераПодробности
Формат кода8 символов из 32-символьного алфавита без неоднозначных знаков (нет 0/O/1/I)
СлучайностьКриптографическая (secrets.choice())
Срок жизни кодаИстекает через 1 час
Ограничение частоты1 запрос на пользователя раз в 10 минут
Лимит ожидающихМаксимум 3 ожидающих кода на платформу
Блокировка5 неудачных попыток одобрения → блокировка на 1 час
Безопасность файловchmod 0600 на всех файлах с данными подключения
ЛогированиеКоды никогда не пишутся в stdout

CLI-команды подключения:

# Список ожидающих и одобренных пользователей
hermes pairing list

# Одобрить код подключения
hermes pairing approve telegram ABC12DEF

# Отозвать доступ пользователя
hermes pairing revoke telegram 123456789

# Очистить все ожидающие коды
hermes pairing clear-pending

Хранилище: данные подключения лежат в ~/.hermes/pairing/ — по JSON-файлу на платформу:

  • {platform}-pending.json — ожидающие запросы на подключение
  • {platform}-approved.json — одобренные пользователи
  • _rate_limits.json — учёт ограничений частоты и блокировок

Изоляция в контейнерах

С бэкендом терминала docker Hermes применяет строгое усиление безопасности к каждому контейнеру.

Флаги безопасности Docker

Каждый контейнер запускается с такими флагами (заданы в tools/environments/docker.py):

_BASE_SECURITY_ARGS = [
    "--cap-drop", "ALL",                          # Сбросить ВСЕ Linux-возможности
    "--cap-add", "DAC_OVERRIDE",                  # root может писать в bind-смонтированные каталоги
    "--cap-add", "CHOWN",                         # Пакетным менеджерам нужно владение файлами
    "--cap-add", "FOWNER",                        # Пакетным менеджерам нужно владение файлами
    "--security-opt", "no-new-privileges",         # Блокировать эскалацию привилегий
    "--pids-limit", "256",                         # Ограничить число процессов
    "--tmpfs", "/tmp:rw,nosuid,size=512m",         # /tmp с лимитом размера
    "--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m",  # /var/tmp без exec
]

SETUID/SETGID в базовый список не включены — они добавляются условно, когда контейнер стартует от root и init/entrypoint должен сбросить привилегии (путь через s6 privilege-drop). Когда контейнер уже работает как непривилегированный --user, они пропускаются. Tmpfs для /run тоже вынесен из базового списка и монтируется отдельно под каждый образ: по умолчанию с усиленным noexec, а для образов с s6-overlay, которые запускаются из /run, — с exec.

Лимиты ресурсов

Ресурсы контейнера настраиваются в ~/.hermes/config.yaml:

terminal:
  backend: docker
  docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
  docker_forward_env: []  # Только явный список разрешений; пусто = секреты не попадают в контейнер
  container_cpu: 1        # ядра CPU
  container_memory: 5120  # МБ (по умолчанию 5 ГБ)
  container_disk: 51200   # МБ (по умолчанию 50 ГБ, требует overlay2 на XFS)
  container_persistent: true  # Сохранять файловую систему между сессиями

Сохранение файловой системы

  • Постоянный режим (container_persistent: true): bind-монтирует /workspace и /root из ~/.hermes/sandboxes/docker/<task_id>/
  • Эфемерный режим (container_persistent: false): использует tmpfs под рабочее пространство — при очистке всё теряется
совет

Для продакшен-развёртываний шлюза берите бэкенд docker, modal или daytona, чтобы изолировать команды агента от хост-системы. Это полностью снимает необходимость в подтверждении опасных команд.

внимание

Если вы добавите имена в terminal.docker_forward_env, эти переменные намеренно прокидываются в контейнер для команд терминала. Удобно для учётных данных под конкретную задачу, например GITHUB_TOKEN, но это же означает, что код внутри контейнера сможет их прочитать и слить наружу.

Сравнение безопасности бэкендов терминала

БэкендИзоляцияПроверка опасных командЛучше всего для
localНет — выполняется на хосте✅ ДаРазработка, доверенные пользователи
sshУдалённая машина✅ ДаЗапуск на отдельном сервере
dockerКонтейнер❌ Пропускается (граница — контейнер)Продакшен-шлюз
singularityКонтейнер❌ ПропускаетсяHPC-среды
modalОблачная песочница❌ ПропускаетсяМасштабируемая облачная изоляция
daytonaОблачная песочница❌ ПропускаетсяПостоянные облачные рабочие пространства

Проброс переменных окружения

И execute_code, и terminal вырезают чувствительные переменные окружения из дочерних процессов, чтобы код, сгенерированный LLM, не смог вытащить учётные данные наружу. Но навыкам, которые объявляют required_environment_variables, такие переменные действительно нужны.

Как это работает

Конкретные переменные проходят сквозь фильтры песочницы двумя путями:

1. Проброс на уровне навыка (автоматически)

Когда навык загружается (через skill_view или команду /skill) и объявляет required_environment_variables, любые из этих переменных, реально выставленные в окружении, автоматически регистрируются для проброса. Отсутствующие переменные (всё ещё в состоянии «нужна настройка») не регистрируются.

# Во frontmatter файла SKILL.md
required_environment_variables:
  - name: TENOR_API_KEY
    prompt: Tenor API key
    help: Get a key from https://developers.google.com/tenor

После загрузки этого навыка TENOR_API_KEY проходит в execute_code, terminal (local) и удалённые бэкенды (Docker, Modal) — настраивать вручную ничего не нужно.

инфо
Docker и Modal

До версии v0.5.1 forward_env у Docker был отдельной системой от проброса навыков. Теперь они объединены — переменные окружения, объявленные навыком, автоматически прокидываются в Docker-контейнеры и песочницы Modal, и добавлять их вручную в docker_forward_env не нужно.

2. Проброс через конфиг (вручную)

Для переменных, которые не объявлены ни одним навыком, добавьте их в terminal.env_passthrough в config.yaml:

terminal:
  env_passthrough:
    - MY_CUSTOM_KEY
    - ANOTHER_TOKEN

Проброс файлов с учётными данными (OAuth-токены и пр.)

Некоторым навыкам в песочнице нужны файлы, а не только переменные окружения — например, Google Workspace хранит OAuth-токены в google_token.json внутри HERMES_HOME активного профиля. Навыки объявляют их во frontmatter:

required_credential_files:
  - path: google_token.json
    description: Google OAuth2 token (created by setup script)
  - path: google_client_secret.json
    description: Google OAuth2 client credentials

При загрузке Hermes проверяет, есть ли эти файлы в HERMES_HOME активного профиля, и регистрирует их для монтирования:

  • Docker: bind-монтирование только для чтения (-v host:container:ro)
  • Modal: монтируется при создании песочницы и синхронизируется перед каждой командой (обрабатывает настройку OAuth прямо посреди сессии)
  • Local: ничего делать не нужно (файлы и так доступны)

Файлы с учётными данными можно перечислить и вручную в config.yaml:

terminal:
  credential_files:
    - google_token.json
    - my_custom_oauth_token.json

Пути отсчитываются от ~/.hermes/. Файлы монтируются в /root/.hermes/ внутри контейнера. Этот список читает tools/credential_files.py (terminal.credential_files) — он лежит под блоком terminal:, но загружается модулем работы с файлами учётных данных, а не базовым бэкендом терминала, поэтому в встроенный снимок DEFAULT_CONFIG он не входит.

Что фильтрует каждая песочница

ПесочницаФильтр по умолчаниюПереопределение пробросом
execute_codeБлокирует переменные, в имени которых есть KEY, TOKEN, SECRET, PASSWORD, CREDENTIAL, PASSWD, AUTH; пропускает только переменные с безопасным префиксом✅ Проброшенные переменные обходят обе проверки
terminal (local)Блокирует явные инфраструктурные переменные Hermes (ключи провайдеров, токены шлюза, API-ключи инструментов)✅ Проброшенные переменные обходят блок-лист
terminal (Docker)По умолчанию никаких переменных окружения хоста✅ Проброшенные переменные + docker_forward_env прокидываются через -e
terminal (Modal)По умолчанию никаких переменных и файлов хоста✅ Файлы учётных данных монтируются; переменные пробрасываются через синхронизацию
MCPБлокирует всё, кроме безопасных системных переменных и явно настроенного env❌ Проброс не действует (используйте конфиг env у MCP)

Что стоит держать в голове по безопасности

  • Проброс касается только тех переменных, которые объявили вы или ваши навыки — для произвольного кода, сгенерированного LLM, защита остаётся прежней
  • Файлы с учётными данными монтируются в Docker-контейнеры только для чтения
  • Skills Guard сканирует содержимое навыка на подозрительные обращения к переменным окружения ещё до установки
  • Отсутствующие/незаданные переменные никогда не регистрируются (нельзя слить то, чего нет)
  • Инфраструктурные секреты Hermes (API-ключи провайдеров, токены шлюза) нельзя добавлять в env_passthrough — для них есть отдельные механизмы

Работа с учётными данными MCP

Подпроцессы MCP-серверов (Model Context Protocol) получают отфильтрованное окружение, чтобы случайно не утекли учётные данные.

Безопасные переменные окружения

С хоста в stdio-подпроцессы MCP передаются только эти переменные:

PATH, HOME, USER, LANG, LC_ALL, TERM, SHELL, TMPDIR

Плюс любые переменные XDG_*. Все остальные переменные окружения (API-ключи, токены, секреты) вырезаются.

Переменные, явно заданные в конфиге env MCP-сервера, передаются:

mcp_servers:
  github:
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-github"]
    env:
      GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..."  # Передаётся только это

Редактирование учётных данных

Сообщения об ошибках от инструментов MCP санируются, прежде чем вернуться в LLM. Следующие шаблоны заменяются на [REDACTED]:

  • GitHub PAT (ghp_...)
  • Ключи в стиле OpenAI (sk-...)
  • Bearer-токены
  • Параметры token=, key=, API_KEY=, password=, secret=

Политика доступа к сайтам

Можно ограничить, к каким сайтам агент обращается через веб- и браузерные инструменты. Это пригодится, чтобы агент не лез к внутренним сервисам, админ-панелям и прочим чувствительным URL.

# В ~/.hermes/config.yaml
security:
  website_blocklist:
    enabled: true
    domains:
      - "*.internal.company.com"
      - "admin.example.com"
    shared_files:
      - "/etc/hermes/blocked-sites.txt"

Если запрашивается заблокированный URL, инструмент возвращает ошибку с пояснением, что домен закрыт политикой. Блок-лист применяется в web_search, web_extract, browser_navigate и во всех инструментах, способных работать с URL.

Полные детали — в разделе Список заблокированных сайтов руководства по конфигурации.

Защита от SSRF

Все инструменты, работающие с URL (веб-поиск, извлечение страниц, зрение, браузер), проверяют URL перед запросом, чтобы исключить атаки Server-Side Request Forgery (SSRF). Среди заблокированных адресов:

  • Частные сети (RFC 1918): 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
  • Loopback: 127.0.0.0/8, ::1
  • Link-local: 169.254.0.0/16 (включая облачные метаданные по 169.254.169.254)
  • CGNAT / общее адресное пространство (RFC 6598): 100.64.0.0/10 (VPN Tailscale, WireGuard)
  • Имена хостов облачных метаданных: metadata.google.internal, metadata.goog
  • Зарезервированные, multicast и неуказанные адреса

Защита от SSRF всегда активна при работе с интернетом, а сбои DNS трактуются как блокировка (fail-closed). Цепочки редиректов перепроверяются на каждом переходе, чтобы исключить обходы через перенаправления.

Намеренное разрешение частных URL

Бывают конфигурации, где доступ к частным/внутренним URL действительно нужен: домашние сети, где home.arpa резолвится в адреса RFC 1918, локальные эндпоинты Ollama/llama.cpp, внутренние вики, отладка облачных метаданных и тому подобное. На такой случай есть глобальный опт-аут:

security:
  allow_private_urls: true   # по умолчанию: false

Когда он включён, веб-инструменты, браузер, загрузка URL для зрения и скачивание медиа шлюзом перестают отклонять адреса RFC 1918 / loopback / link-local / CGNAT / облачных метаданных. Это сознательно проведённая граница доверия — включайте её только на машинах, где запуск агентом произвольных URL из prompt injection против локальной сети считается приемлемым риском. На публичных шлюзах оставляйте опцию выключенной.

Защита по подстроке хоста (она ловит похожие на настоящие Unicode-домены, даже если за ними публичный IP) остаётся включённой вне зависимости от этой настройки.

Сканирование безопасности перед выполнением через Tirith

Hermes использует tirith для сканирования содержимого команд на уровне контента перед запуском. Tirith ловит угрозы, которые упускает одно лишь сопоставление по шаблонам:

  • Подмену URL через гомоглифы (атаки на интернационализированные домены)
  • Шаблоны передачи в интерпретатор (curl | bash, wget | sh)
  • Атаки через инъекцию в терминал

Tirith при первом использовании автоматически ставится из релизов на GitHub с проверкой контрольной суммы SHA-256 (и проверкой происхождения через cosign, если cosign доступен).

# В ~/.hermes/config.yaml
security:
  tirith_enabled: true       # Включить/отключить сканирование tirith (по умолчанию: true)
  tirith_path: "tirith"      # Путь к бинарнику tirith (по умолчанию: поиск в PATH)
  tirith_timeout: 5          # Тайм-аут подпроцесса в секундах
  tirith_fail_open: true     # Разрешать выполнение, когда tirith недоступен (по умолчанию: true)

Когда tirith_fail_open равен true (по умолчанию), команды выполняются, если tirith не установлен или превысил тайм-аут. В средах с повышенными требованиями к безопасности ставьте false, чтобы блокировать команды, пока tirith недоступен.

Tirith поставляется с готовыми бинарниками для Linux (x86_64 / aarch64) и macOS (x86_64 / arm64). На платформах без готового бинарника (Windows и т. п.) tirith молча пропускается — защита по шаблонам всё равно работает, и CLI не показывает баннер «недоступно». Чтобы пользоваться tirith под Windows, запускайте Hermes в WSL.

Вердикт tirith вплетён в поток подтверждения: безопасные команды проходят, а подозрительные и заблокированные вызывают запрос подтверждения с полными находками tirith (серьёзность, заголовок, описание, более безопасные альтернативы). Пользователь может одобрить или отклонить — по умолчанию выбран отказ, чтобы автономные сценарии оставались защищёнными.

Защита контекстных файлов от инъекций

Контекстные файлы (AGENTS.md, .cursorrules, SOUL.md) сканируются на prompt injection перед тем, как попасть в системный промпт. Сканер проверяет:

  • Инструкции игнорировать/отбросить предыдущие указания
  • Скрытые HTML-комментарии с подозрительными ключевыми словами
  • Попытки прочитать секреты (.env, credentials, .netrc)
  • Слив учётных данных через curl
  • Невидимые Unicode-символы (zero-width пробелы, переопределения направления текста)

Заблокированные файлы показывают предупреждение:

[BLOCKED: AGENTS.md contained potential prompt injection (prompt_injection). Content not loaded.]

Практики безопасного развёртывания в продакшене

Чек-лист развёртывания шлюза

  1. Задайте явные списки разрешений — никогда не используйте GATEWAY_ALLOW_ALL_USERS=true в продакшене
  2. Берите контейнерный бэкенд — выставьте terminal.backend: docker в config.yaml
  3. Ограничьте лимиты ресурсов — задайте разумные лимиты CPU, памяти и диска
  4. Храните секреты надёжно — держите API-ключи в ~/.hermes/.env с правильными правами доступа
  5. Включите парное подключение DM — по возможности используйте коды подключения вместо захардкоженных ID
  6. Просматривайте список разрешений команд — периодически проверяйте command_allowlist в config.yaml
  7. Задайте terminal.cwd — не давайте агенту работать из чувствительных каталогов
  8. Запускайте не от root — никогда не запускайте шлюз под root
  9. Следите за логами — проверяйте ~/.hermes/logs/ на попытки несанкционированного доступа
  10. Обновляйтесь — регулярно запускайте hermes update ради патчей безопасности

Защита API-ключей

# Выставить правильные права на файл .env
chmod 600 ~/.hermes/.env

# Держите отдельные ключи под разные сервисы
# Никогда не коммитьте файлы .env в систему контроля версий

Сетевая изоляция

Для максимальной безопасности запускайте шлюз на отдельной машине или VM. Выставьте terminal.backend: ssh в config.yaml, а данные хоста передайте через переменные окружения в ~/.hermes/.env:

# ~/.hermes/config.yaml
terminal:
  backend: ssh
# ~/.hermes/.env
TERMINAL_SSH_HOST=agent-worker.local
TERMINAL_SSH_USER=hermes
TERMINAL_SSH_KEY=~/.ssh/hermes_agent_key

Детали SSH-подключения лежат в .env (не в config.yaml), поэтому они не попадают в систему контроля версий и не уезжают вместе с экспортом профиля. Так подключения шлюза к мессенджерам остаются отдельно от выполнения команд агентом.

Проверка по advisory о supply-chain

Hermes несёт встроенный сканер advisory: он отмечает Python-пакеты в активном venv, которые совпадают с подобранным каталогом заведомо скомпрометированных версий (supply-chain-черви вроде отравления mistralai 2.4.6 в мае 2026). Реализация лежит в hermes_cli/security_advisories.py.

Как он срабатывает:

  • Баннер при старте CLI. Если сработало хоть одно advisory, печатается однострочное предупреждение с отсылкой к hermes doctor за полным планом исправления.
  • hermes doctor. Выводит каждое активное advisory с указанием версий и инструкцией из 2–4 шагов по устранению.
  • Старт шлюза. Пишется в gateway.log; на первое интерактивное сообщение приходит короткий баннер для оператора.

У каждого advisory есть стабильный id. Прочитав его и приняв меры, можно убрать его навсегда:

hermes doctor --ack <advisory-id>

Подтверждение сохраняется в config.security.acked_advisories и переживает перезапуск. Старые advisory из каталога намеренно не удаляются — оставляя их на месте, мы предупреждаем свежие установки про исторически отравленные версии, которые могут до сих пор лежать в кэше приватного зеркала.

Сама проверка обходится только стандартной библиотекой и сводится к одному вызову importlib.metadata.version() на каждое advisory, так что запускать её при каждом старте безопасно.

Ленивая установка опциональных зависимостей

Многие функции (Mistral TTS, ElevenLabs, память Honcho, Bedrock, Slack, Matrix, …) опираются на Python-пакеты, которые нужны не каждому. Hermes ставит их лениво, при первом использовании, а не жадно через hermes-agent[all]. Реализация лежит в tools/lazy_deps.py.

Какой компромисс это решает:

  • Хрупкость. Когда транзитивная зависимость одного extra становится недоступна на PyPI (карантин из-за вредоносного кода, отзыв, сломанная загрузка), весь resolve [all] падает, и свежие установки молча скатываются в урезанный набор — теряя разом 10+ никак не связанных extra. Ленивая установка изолирует каждый бэкенд, чтобы одна отравленная зависимость не ломала посторонние функции.
  • Раздувание. Тот, кто общается лишь с одним провайдером, больше не тянет сотни пакетов, которые ему никогда не понадобятся.

Как это работает:

  1. Модуль бэкенда вызывает ensure("feature.name") в начале своего пути первого импорта.
  2. Если зависимостей нет, ensure проверяет security.allow_lazy_installs в config.yaml (по умолчанию true) и выполняет pip install в рамках venv для спецификаций из списка разрешений.
  3. Если установка падает или пользователь отключил ленивые установки, вызов поднимает FeatureUnavailable с реальным stderr от pip и отсылкой к hermes tools.

Гарантии безопасности, которые обеспечивает tools/lazy_deps.py:

ГарантияЧто это значит
Только в рамках venvУстановка идёт в sys.executable активного venv — никогда в системный Python
Только по имени с PyPIСпецификации принимают синтаксис вида "package>=1.0,<2". Никаких --index-url, git+https:// или путей file: — вредоносный config.yaml не сможет перенаправить установку
Список разрешенийЧерез этот путь ставятся только спецификации из встроенной карты LAZY_DEPS. Опечатка в имени функции НЕ даёт права «установить что угодно»
Опт-аутПоставьте security.allow_lazy_installs: false, чтобы полностью отключить установки в рантайме. Удобно для сетей с ограничениями или строгих требований к безопасности
Без тихих повторовСбои всплывают как FeatureUnavailable — без кэширования плохого состояния, без шторма повторных попыток

Чтобы отключить установки в рантайме:

# ~/.hermes/config.yaml
security:
  allow_lazy_installs: false

Когда они отключены, бэкенды, которым нужны опциональные зависимости, скажут пользователю установить их вручную (pip install …) или выбрать другой бэкенд через hermes tools.

ESC