feat: подбор угроз — инвертированная логика (все угрозы минус неактуальные)

- threat_rules: по умолчанию актуальны ВСЕ угрозы БДУ ФСТЭК, исключаются
  заведомо неактуальные: нет интернета → убраны удалённый доступ/веб/почта/
  беспроводные/DDoS/облачные/грид; АСУ → убраны BIOS/грид/виртуализация/браузер
  (теперь 170-200 угроз на объект вместо 15 «самых похожих»)
- _has_internet уточнён: «Отсутствует» у оператора / «изолирован» / «не требуется»
  → внешней сети нет; сеть общего пользования или реальный оператор → есть
- verify.suggest_threats_ai: свежий перечень ФСТЭК (227 угроз) передаётся в промт,
  ИИ выбирает из него, а не из устаревших знаний; + человекочитаемая сводка объекта
- ui: кнопки программа/ИИ без лимита 20; после подбора угрозы сохраняются в модель
  (_collect_is) — при открытии словаря галочки выбранных угроз стоят
- models.from_dict: фильтрация неизвестных полей вложенных объектов — старые файлы
  с удалёнными полями (attacker_characteristics) больше не падают
This commit is contained in:
2026-08-04 17:59:44 +04:00
parent 8569b478d4
commit 6c2e4a0c38
4 changed files with 108 additions and 58 deletions
+29 -13
View File
@@ -474,33 +474,49 @@ def compare_company_values(old_data, new_data):
return diffs
def suggest_threats_ai(security_data, api_key, model='deepseek-chat', base_url=None):
def suggest_threats_ai(security_data, api_key, model='deepseek-chat', base_url=None,
threats=None):
"""ИИ предлагает актуальные угрозы БИ для объекта КИИ по его характеристикам.
security_data — dict: name, description, object_type, sphere, architecture,
attacker_category, has_internet (True/False), etc.
threats — актуальный перечень угроз БДУ ФСТЭК (список строк «УБИ.xxx …»).
Передаётся в промт целиком, чтобы ИИ выбирал из свежего списка,
а не из своего устаревшего.
Возвращает (список_угроз, ошибка).
"""
if not api_key:
return None, "Не задан API-ключ ИИ (Файл → Настройки → API)"
internet = 'да' if security_data.get('has_internet', True) else 'нет'
if threats:
threat_list_text = "\n".join(f"{i+1}. {t}" for i, t in enumerate(threats))
else:
threat_list_text = "(перечень не предоставлен — используй знания об актуальном перечне БДУ ФСТЭК)"
system = (
"Ты — эксперт ФСТЭК по безопасности критической информационной инфраструктуры "
"(187-ФЗ, приказ ФСТЭК № 236). На основе характеристик объекта КИИ составь "
"перечень АКТУАЛЬНЫХ угроз безопасности информации из БДУ ФСТЭК "
"(перечень угроз УБИ.xxx). Верни ТОЛЬКО JSON: "
'{"threats": ["УБИ.xxx Название угрозы", ...]} — от 8 до 15 угроз, '
"без пояснений и комментариев."
"(187-ФЗ, приказ ФСТЭК № 236). На основе характеристик объекта КИИ выбери из "
"предоставленного перечня АКТУАЛЬНЫЕ угрозы безопасности информации и верни "
"ТОЛЬКО JSON: "
'{"threats": ["УБИ.xxx Название угрозы", ...]} — от 10 до 40 угроз, '
"без пояснений и комментариев. Не выдумывай угрозы, которых нет в перечне; "
"используй полные строки из перечня (код + название) как они есть."
)
user = (
"Характеристики объекта КИИ:\n"
f"{json.dumps(security_data, ensure_ascii=False)}\n\n"
"Актуальный перечень угроз БДУ ФСТЭК:\n"
f"{threat_list_text}"
)
user = json.dumps(security_data, ensure_ascii=False)
js, err = _ai_json(api_key, model, base_url, system, user)
if err:
return None, err
threats = js.get('threats') or []
if isinstance(threats, str):
threats = [threats]
threats = [str(t).strip() for t in threats if str(t).strip()]
return threats, None
threats_out = js.get('threats') or []
if isinstance(threats_out, str):
threats_out = [threats_out]
threats_out = [str(t).strip() for t in threats_out if str(t).strip()]
return threats_out, None
def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None):