# 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`): ```yaml 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`. Ключ оставлен в конфиге как документация. ## Как снять правило ```bash TIRITH=~/.hermes/bin/tirith # 1. Посмотреть, что сработало последним $TIRITH trust last # или: jq . ~/.local/share/tirith/last_trigger.json # 2. Узнать, что делает правило $TIRITH explain --rule # 3. Добавить в trust (pattern + scope правила) $TIRITH trust add --rule --ttl 3650d # 4. Проверить $TIRITH trust list $TIRITH check "<команда>" # пустой вывод = проходит ``` **Замечания по синтаксису:** - `tirith explain ` — **не работает**, нужен флаг: `tirith explain --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` — *потребитель*, не *источник*. Искать надо в: 1. `~/.local/share/tirith/last_trigger.json` — что именно сработало (`rule_ids`) 2. `~/.local/share/tirith/log.jsonl` — вся история срабатываний 3. `tirith explain --rule ` — описание правила 4. `tirith trust list` — что уже доверено Тот же принцип, что и с `security.tirith_allowlist`: в конфиге ключ есть, но система его не читает. **Наличие ключа в конфиге ≠ рабочая настройка.** ## Связанное - [[hermes-git-repo]] — структура репо, .gitignore - [[hermes-fork-vs-upstream]] — расхождение с апстримом