Безопасность
В основе Hermes Agent лежит модель безопасности по принципу эшелонированной защиты. На этой странице разобраны все границы безопасности: от подтверждения команд до изоляции в контейнерах и авторизации пользователей в мессенджерах.
Обзор
Модель безопасности состоит из семи слоёв:
- Авторизация пользователей — кто вообще может писать агенту (списки разрешений, парное подключение через DM)
- Подтверждение опасных команд — человек в контуре, когда речь о разрушительных операциях
- Изоляция в контейнерах — песочница на Docker/Singularity/Modal с усиленными настройками
- Фильтрация учётных данных для MCP — изоляция переменных окружения для подпроцессов MCP
- Сканирование контекстных файлов — поиск prompt injection в файлах проекта
- Изоляция между сессиями — сессии не видят данные и состояние друг друга; пути хранения для cron-задач защищены от атак с обходом каталога (path traversal)
- Санитизация входных данных — параметры рабочего каталога в бэкендах терминала сверяются со списком разрешений, чтобы исключить инъекцию в шелл
Подтверждение опасных команд
Перед запуском любой команды 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 спрашивают перед сбросом состояния
Полный набор ключей:
| Ключ | По умолчанию | За что отвечает |
|---|---|---|
mode | manual | Политика подтверждения опасных shell-команд — см. таблицу ниже. |
timeout | 60 | Сколько секунд Hermes ждёт ответа на запрос подтверждения, прежде чем сработает тайм-аут. |
cron_mode | deny | Как cron-задачи ведут себя в headless-режиме, когда натыкаются на запрос подтверждения опасной команды. deny блокирует команду (агент должен искать другой путь); approve автоматически одобряет всё в контексте cron. |
mcp_reload_confirm | true | Если включено, /reload-mcp спрашивает перед пересборкой набора инструментов MCP. Пересборка сбрасывает кэш промпта у провайдера (схемы инструментов лежат в системном промпте), поэтому следующее сообщение заново отправит полный объём входных токенов. Те, кто нажимает Always Approve, переводят этот ключ в false. |
destructive_slash_confirm | true | Если включено, разрушительные 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 обходит все запросы на подтверждение опасных команд в текущей сессии. Включить его можно тремя способами:
- Флаг CLI: запустите сессию через
hermes --yoloилиhermes chat --yolo - Slash-команда: введите
/yoloво время сессии, чтобы включить или выключить режим - Переменная окружения: установите
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/DATABASE | SQL DROP |
DELETE FROM (без WHERE) | SQL DELETE без WHERE |
TRUNCATE TABLE | SQL 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 rm | xargs вместе с rm |
find -exec rm / find -delete | find с разрушительными действиями |
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() проверяет в таком порядке:
- Флаг «разрешить всех» для платформы (например,
DISCORD_ALLOW_ALL_USERS=true) - Список одобренных через парное подключение DM (пользователи, одобренные по коду подключения)
- Списки разрешений конкретной платформы (например,
TELEGRAM_ALLOWED_USERS=12345,67890) - Глобальный список разрешений (
GATEWAY_ALLOWED_USERS=12345,67890) - Глобальный «разрешить всех» (
GATEWAY_ALLOW_ALL_USERS=true) - По умолчанию: отказ
Списки разрешений по платформам
Задайте разрешённые 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.
Как это работает:
- Незнакомый пользователь пишет боту в личные сообщения
- Бот отвечает 8-символьным кодом подключения
- Владелец бота выполняет в CLI
hermes pairing approve <platform> <code> - Пользователь получает постоянное одобрение для этой платформы
Поведение для неавторизованных личных сообщений настраивается в ~/.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) — настраивать вручную ничего не нужно.
До версии 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.]
Практики безопасного развёртывания в продакшене
Чек-лист развёртывания шлюза
- Задайте явные списки разрешений — никогда не используйте
GATEWAY_ALLOW_ALL_USERS=trueв продакшене - Берите контейнерный бэкенд — выставьте
terminal.backend: dockerв config.yaml - Ограничьте лимиты ресурсов — задайте разумные лимиты CPU, памяти и диска
- Храните секреты надёжно — держите API-ключи в
~/.hermes/.envс правильными правами доступа - Включите парное подключение DM — по возможности используйте коды подключения вместо захардкоженных ID
- Просматривайте список разрешений команд — периодически проверяйте
command_allowlistв config.yaml - Задайте
terminal.cwd— не давайте агенту работать из чувствительных каталогов - Запускайте не от root — никогда не запускайте шлюз под root
- Следите за логами — проверяйте
~/.hermes/logs/на попытки несанкционированного доступа - Обновляйтесь — регулярно запускайте
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. Ленивая установка изолирует каждый бэкенд, чтобы одна отравленная зависимость не ломала посторонние функции. - Раздувание. Тот, кто общается лишь с одним провайдером, больше не тянет сотни пакетов, которые ему никогда не понадобятся.
Как это работает:
- Модуль бэкенда вызывает
ensure("feature.name")в начале своего пути первого импорта. - Если зависимостей нет,
ensureпроверяетsecurity.allow_lazy_installsвconfig.yaml(по умолчаниюtrue) и выполняетpip installв рамках venv для спецификаций из списка разрешений. - Если установка падает или пользователь отключил ленивые установки, вызов поднимает
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.