Проблема
Простейшая агентная схема выглядит заманчиво: модель видит вывод nmap, просит запустить ещё один инструмент, получает результат и продолжает анализ. Для лаборатории это удобно, для коммерческого продукта опасно.
Если LLM может формировать произвольную команду, prompt injection из HTML, banner, robots.txt или tool output становится частью управляющего контура. Именно поэтому автономность должна быть не текстовой, а policy-driven.
Решение
Вместо свободного shell нужен каталог техник. Каждая техника описывает prerequisites, scope rules, side effects, timeout, rate budget, evidence schema, validator и stop conditions.
Модель возвращает только JSON: hypothesis, facts_used, technique_id, expected_evidence, risk_class. Trusted adapter превращает technique_id в команду, policy engine решает можно/нельзя, validator подтверждает результат.
Что проверить руками
- Отвергаются ли неизвестные technique_id и сырые команды.
- Сохраняется ли action ledger: кто запросил, какая политика сработала, какой evidence hash получен.
- Есть ли timeout/fallback, если локальная или внешняя модель недоступна.
- Не попадают ли cookies, tokens, PII и raw response body в prompt.
Как это должно попадать в отчёт
- AI-раздел должен показывать не скрытые рассуждения, а decision records: гипотеза, факты, разрешённая техника, результат, причина отказа или подтверждения.
- Клиенту важна воспроизводимость: можно открыть finding и увидеть, что модель не придумала риск, а только помогла выбрать следующую проверку.
Безопасный пример: policy-gate для AI-плана
from pydantic import BaseModel, Field, ValidationError
ALLOWED_TECHNIQUES = {
"http.headers.readonly",
"api.inventory.har",
"api.bola_read.compare_roles",
"tls.configuration.audit",
}
class PlannedAction(BaseModel):
hypothesis: str = Field(min_length=10, max_length=500)
technique_id: str
facts_used: list[str] = Field(default_factory=list, max_length=12)
expected_evidence: list[str] = Field(default_factory=list, max_length=8)
mode: str = "read_only"
def policy_decision(raw_plan: dict) -> tuple[bool, str]:
try:
plan = PlannedAction.model_validate(raw_plan)
except ValidationError as exc:
return False, f"schema rejected: {exc.errors()[0]['msg']}"
if plan.technique_id not in ALLOWED_TECHNIQUES:
return False, "unknown technique"
if plan.mode != "read_only":
return False, "state-changing actions require separate approval"
return True, "approved for trusted adapter"
print(policy_decision({
"hypothesis": "The API may expose another user's invoice through object id reuse.",
"technique_id": "api.bola_read.compare_roles",
"facts_used": ["HAR contains /api/invoices/{id}", "two test roles exist"],
"expected_evidence": ["owner trace", "viewer trace", "body hash delta"],
}))