feat(agent): агент логов — фильтр, LLM-страж, планировщик grep #46

Merged
Eleboba merged 1 commits from feat/18-agent into dev 2026-10-17 16:02:43 +03:00
Owner

Closes #18 · мержить после #45 (ветка от feat/16-logs, до мержа #45 в диффе видны и его коммиты)

Конвейер (SPEC §4.3.1, ADR-007, ADR-013)

промт ─► prefilter ─✗─► отказ + 🛡 админам
           │✓
           ▼
        guard (LLM, JSON {verdict, reason}) ─ attack / мусор ─► отказ + 🛡
           │safe
           ▼
        planner (LLM, JSON AgentLogQuery) ─ не прошёл валидацию ─► «не смог построить запрос»
           │
           ▼
        grep (#16) ─► ответ: построенная команда grep + строки
  • prefilter (config/agent_rules.yaml): длина ≤ 500; просьбы выполнить команду; shell-метасимволы с намерением исполнения; системные файлы и секреты (.env, ~/.ssh, /etc/, api key); prompt injection («игнорируй инструкции», «ты теперь…», «system prompt»). Слова вроде password или token не режутся: «password authentication failed» — законная строка лога, такие запросы оценивает страж.
  • guard: отдельный вызов с узким промптом (config/prompts/guard.md). Невалидный JSON или неизвестный вердикт считаются атакой. Недоступный LLM — это «попробуйте позже», не атака.
  • planner (config/prompts/planner.md): получает только вопрос, список контейнеров и время; логов не видит. Ответ — строго AgentLogQuery: контейнер из белого списка, период ≤ 7 дней, шаблон ≤ 200 символов компилируется и не содержит вложенных квантификаторов ((a+)+, (a|aa)*), контекст ≤ 5, лимит ≤ 500, лишние поля запрещены.
  • Бот: «🤖 Спросить агента» в карточке контейнера логов; ответ показывает grep -iE '…', контейнер, период и строки. При атаке пользователь получает вежливый отказ без деталей, а администраторы — 🛡 с текстом, автором и причиной.
  • Квота 30 вопросов в сутки; журнал agent_requests (вердикт, причина, запрос, токены) и log_queries (mode=agent).
  • При LLM_PROVIDER=mock агент выключен, кнопка отвечает «Агент не настроен»: mock возвращает стражу {}, и любой вопрос был бы атакой.

Как проверено

  • 273 теста (51 новый): атаки (injection, «покажи .env», rm -rf, $(curl …), ~/.ssh, system prompt, > 500 символов), ложные срабатывания (обычные вопросы, «password authentication failed»), мусор от стража и планировщика, чужой контейнер, ReDoS-шаблон от планировщика, недоступный LLM, квота, алерт админам, а не участнику.
  • Покрытие 92%, ruff, mypy strict.

Нужно решить

Какой LLM подключить на сервере: нужна модель с structured output (response_format: json_schema). Без него агент на сервере выключен, остальное работает.

🤖 Generated with Claude Code

Closes #18 · **мержить после #45** (ветка от `feat/16-logs`, до мержа #45 в диффе видны и его коммиты) ## Конвейер (SPEC §4.3.1, ADR-007, ADR-013) ``` промт ─► prefilter ─✗─► отказ + 🛡 админам │✓ ▼ guard (LLM, JSON {verdict, reason}) ─ attack / мусор ─► отказ + 🛡 │safe ▼ planner (LLM, JSON AgentLogQuery) ─ не прошёл валидацию ─► «не смог построить запрос» │ ▼ grep (#16) ─► ответ: построенная команда grep + строки ``` - **prefilter** (`config/agent_rules.yaml`): длина ≤ 500; просьбы выполнить команду; shell-метасимволы с намерением исполнения; системные файлы и секреты (`.env`, `~/.ssh`, `/etc/`, api key); prompt injection («игнорируй инструкции», «ты теперь…», «system prompt»). Слова вроде `password` или `token` не режутся: «password authentication failed» — законная строка лога, такие запросы оценивает страж. - **guard**: отдельный вызов с узким промптом (`config/prompts/guard.md`). Невалидный JSON или неизвестный вердикт считаются атакой. Недоступный LLM — это «попробуйте позже», не атака. - **planner** (`config/prompts/planner.md`): получает только вопрос, список контейнеров и время; **логов не видит**. Ответ — строго `AgentLogQuery`: контейнер из белого списка, период ≤ 7 дней, шаблон ≤ 200 символов компилируется и не содержит вложенных квантификаторов (`(a+)+`, `(a|aa)*`), контекст ≤ 5, лимит ≤ 500, лишние поля запрещены. - **Бот**: «🤖 Спросить агента» в карточке контейнера логов; ответ показывает `grep -iE '…'`, контейнер, период и строки. При атаке пользователь получает вежливый отказ без деталей, а администраторы — 🛡 с текстом, автором и причиной. - **Квота** 30 вопросов в сутки; журнал `agent_requests` (вердикт, причина, запрос, токены) и `log_queries` (`mode=agent`). - При `LLM_PROVIDER=mock` агент выключен, кнопка отвечает «Агент не настроен»: mock возвращает стражу `{}`, и любой вопрос был бы атакой. ## Как проверено - 273 теста (51 новый): атаки (injection, «покажи .env», `rm -rf`, `$(curl …)`, `~/.ssh`, system prompt, > 500 символов), ложные срабатывания (обычные вопросы, «password authentication failed»), мусор от стража и планировщика, чужой контейнер, ReDoS-шаблон от планировщика, недоступный LLM, квота, алерт админам, а не участнику. - Покрытие 92%, ruff, mypy strict. ## Нужно решить Какой LLM подключить на сервере: нужна модель с structured output (`response_format: json_schema`). Без него агент на сервере выключен, остальное работает. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Eleboba added 2 commits 2026-10-17 15:03:46 +03:00
feat(logs): раздел «Логи» — хвост и свой grep (#16)
CI / ruff · mypy · pytest (pull_request) Successful in 9m40s
f89338e015
- app/logs/grep.py: подмножество grep (шаблон, -i -v -E -c -C N), разбор
  shlex без исполнения, BRE → regex, таймаут на строку и бюджет на поиск,
  лимиты 200 символов / 500 строк вывода
- app/logs/journal.py: каждый поиск в log_queries (L-6)
- бот: выбор контейнера, хвост 50/200 строк или 5 мин/1 ч/24 ч, свой grep
  по 24 ч, длинный результат — файлом .log (L-5)
- логи Docker: не больше 50 000 строк и 5 МБ, сохраняются новейшие
- fix: кнопки меню и /menu, /help больше не проглатываются шагами ввода
  (имя файла, содержимое, сообщение коммита, grep) — фильтр free_input

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- prefilter: правила из config/agent_rules.yaml (длина, команды, shell,
  секреты и системные файлы, prompt injection) — без LLM
- guard: отдельный вызов, JSON {verdict, reason}; невалидный ответ = attack,
  недоступный LLM — «попробуйте позже», а не атака
- planner: только AgentLogQuery (контейнер из белого списка, ≤ 7 д, шаблон
  ≤ 200 без вложенных квантификаторов, лимит ≤ 500); логи в LLM не идут
- бот: «🤖 Спросить агента» → показывает построенный grep и строки;
  атака — вежливый отказ и 🛡 администраторам; квота 30 в сутки
- журнал agent_requests + log_queries (mode=agent)
- при LLM_PROVIDER=mock агент выключен

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eleboba merged commit 76f9cf2e06 into dev 2026-10-17 16:02:43 +03:00
Sign in to join this conversation.