From baa8f095d07168ecea7f891a00eec2723e7805e1 Mon Sep 17 00:00:00 2001 From: LocoAgent Date: Tue, 4 Aug 2026 17:36:02 +0400 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4=D0=BA=D0=B8=20=C2=AB?= =?UTF-8?q?=D0=91=D0=B5=D0=B7=D0=BE=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=C2=BB=20=D0=B8=D0=B7=20DokoGen=20187-=D0=A4=D0=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - security_tab_187_reference.py — код вкладки (секция построения + методы-обработчики + ИИ-проверка) для переноса интерфейса - словари: attacker_categories, incidents, criteria_indicators, org_measures, tech_measures, threats, threats_fstec (+дата актуализации) - fstec_threats.py — скачивание перечня угроз с bdu.fstec.ru - verify.py: check_security_ai — ИИ-проверка безопасности по 236 приказу --- dokogen/dictionaries/attacker_categories.json | 5 + dokogen/dictionaries/criteria_indicators.json | 7 + dokogen/dictionaries/incidents.json | 8 + dokogen/dictionaries/org_measures.json | 9 + dokogen/dictionaries/tech_measures.json | 14 + dokogen/dictionaries/threats.json | 12 + dokogen/dictionaries/threats_fstec.json | 229 +++++++++ dokogen/dictionaries/threats_fstec_date.txt | 1 + dokogen/fstec_threats.py | 149 ++++++ dokogen/security_tab_187_reference.py | 480 ++++++++++++++++++ dokogen/verify.py | 39 ++ 11 files changed, 953 insertions(+) create mode 100644 dokogen/dictionaries/attacker_categories.json create mode 100644 dokogen/dictionaries/criteria_indicators.json create mode 100644 dokogen/dictionaries/incidents.json create mode 100644 dokogen/dictionaries/org_measures.json create mode 100644 dokogen/dictionaries/tech_measures.json create mode 100644 dokogen/dictionaries/threats.json create mode 100644 dokogen/dictionaries/threats_fstec.json create mode 100644 dokogen/dictionaries/threats_fstec_date.txt create mode 100644 dokogen/fstec_threats.py create mode 100644 dokogen/security_tab_187_reference.py diff --git a/dokogen/dictionaries/attacker_categories.json b/dokogen/dictionaries/attacker_categories.json new file mode 100644 index 0000000..08b757b --- /dev/null +++ b/dokogen/dictionaries/attacker_categories.json @@ -0,0 +1,5 @@ +[ + "внешний нарушитель", + "внутренний нарушитель", + "внешний и внутренний нарушитель" +] diff --git a/dokogen/dictionaries/criteria_indicators.json b/dokogen/dictionaries/criteria_indicators.json new file mode 100644 index 0000000..1487a52 --- /dev/null +++ b/dokogen/dictionaries/criteria_indicators.json @@ -0,0 +1,7 @@ +[ + "Социальная значимость — количество граждан, на которые может повлиять нарушение", + "Политическая значимость — международный или внутриполитический резонанс", + "Экономическая значимость — возможный размер ущерба (в рублях)", + "Экологическая значимость — возможный ущерб окружающей среде", + "Значимость для обороны страны, безопасности государства и правопорядка" +] diff --git a/dokogen/dictionaries/incidents.json b/dokogen/dictionaries/incidents.json new file mode 100644 index 0000000..9096d78 --- /dev/null +++ b/dokogen/dictionaries/incidents.json @@ -0,0 +1,8 @@ +[ + "отказ в обслуживании", + "несанкционированный доступ", + "утечка данных (нарушение конфиденциальности)", + "модификация (подмена) данных", + "нарушение функционирования технических средств", + "несанкционированное использование вычислительных ресурсов объекта" +] diff --git a/dokogen/dictionaries/org_measures.json b/dokogen/dictionaries/org_measures.json new file mode 100644 index 0000000..a5a5b2e --- /dev/null +++ b/dokogen/dictionaries/org_measures.json @@ -0,0 +1,9 @@ +[ + "Установление контролируемой зоны", + "Контроль физического доступа к объекту", + "Разработка документов (регламентов, инструкций, руководств) по обеспечению безопасности объекта", + "Назначение ответственных за обеспечение безопасности объекта", + "Обучение и инструктаж персонала", + "Регламентация действий при компьютерных инцидентах", + "Утверждение перечня лиц, имеющих доступ к объекту" +] diff --git a/dokogen/dictionaries/tech_measures.json b/dokogen/dictionaries/tech_measures.json new file mode 100644 index 0000000..55805aa --- /dev/null +++ b/dokogen/dictionaries/tech_measures.json @@ -0,0 +1,14 @@ +[ + "Идентификация и аутентификация пользователей", + "Управление доступом", + "Ограничение программной среды", + "Антивирусная защита", + "Защита информации от несанкционированного доступа (СЗИ от НСД)", + "Регистрация и мониторинг событий безопасности", + "Межсетевое экранирование", + "Обнаружение вторжений (СОВ)", + "Криптографическая защита информации (СКЗИ)", + "Защита среды виртуализации", + "Резервное копирование и восстановление", + "Защита технических средств и линий связи" +] diff --git a/dokogen/dictionaries/threats.json b/dokogen/dictionaries/threats.json new file mode 100644 index 0000000..db774ce --- /dev/null +++ b/dokogen/dictionaries/threats.json @@ -0,0 +1,12 @@ +[ + "Угроза несанкционированного доступа к информационным ресурсам объекта", + "Угроза нарушения функционирования объекта (отказ в обслуживании)", + "Угроза утечки (нарушения конфиденциальности) информации", + "Угроза модификации (подмены) данных", + "Угроза внедрения вредоносного кода", + "Угроза несанкционированного использования вычислительных ресурсов", + "Угроза перехвата (прослушивания) сетевого трафика", + "Угроза социальной инженерии", + "Угроза использования уязвимостей программного обеспечения", + "Угроза физического воздействия на технические средства" +] diff --git a/dokogen/dictionaries/threats_fstec.json b/dokogen/dictionaries/threats_fstec.json new file mode 100644 index 0000000..f1335e3 --- /dev/null +++ b/dokogen/dictionaries/threats_fstec.json @@ -0,0 +1,229 @@ +[ + "Угроза автоматического распространения вредоносного кода в грид-системе", + "Угроза агрегирования данных, передаваемых в грид-системе", + "Угроза использования слабостей криптографических алгоритмов и уязвимостей в программном обеспечении их реализации", + "Угроза аппаратного сброса пароля BIOS", + "Угроза внедрения вредоносного кода в BIOS", + "Угроза внедрения кода или данных", + "Угроза воздействия на программы с высокими привилегиями", + "Угроза восстановления и/или повторного использования аутентификационной информации", + "Угроза восстановления предыдущей уязвимой версии BIOS", + "Угроза выхода процесса за пределы виртуальной машины", + "Угроза деавторизации санкционированного клиента беспроводной сети", + "Угроза деструктивного изменения конфигурации/среды окружения программ", + "Угроза деструктивного использования декларированного функционала BIOS", + "Угроза длительного удержания вычислительных ресурсов пользователями", + "Угроза доступа к защищаемым файлам с использованием обходного пути", + "Угроза доступа к локальным файлам сервера при помощи URL", + "Угроза доступа/перехвата/изменения HTTP cookies", + "Угроза загрузки нештатной операционной системы", + "Угроза заражения DNS-кеша", + "Угроза злоупотребления возможностями, предоставленными потребителям облачных услуг", + "Угроза злоупотребления доверием потребителей облачных услуг", + "Угроза избыточного выделения оперативной памяти", + "Угроза изменения компонентов информационной (автоматизированной) системы", + "Угроза изменения режимов работы аппаратных элементов компьютера", + "Угроза изменения системных и глобальных переменных", + "Угроза искажения XML-схемы", + "Угроза искажения вводимой и выводимой на периферийные устройства информации", + "Угроза использования альтернативных путей доступа к ресурсам", + "Угроза использования вычислительных ресурсов суперкомпьютера «паразитными» процессами", + "Угроза использования информации идентификации/аутентификации, заданной по умолчанию", + "Угроза использования механизмов авторизации для повышения привилегий", + "Угроза использования поддельных цифровых подписей BIOS", + "Угроза использования слабостей кодирования входных данных", + "Угроза использования слабостей протоколов сетевого/локального обмена данными", + "Угроза использования слабых криптографических алгоритмов BIOS", + "Угроза исследования механизмов работы программы", + "Угроза исследования приложения через отчёты об ошибках", + "Угроза исчерпания вычислительных ресурсов хранилища больших данных", + "Угроза исчерпания запаса ключей, необходимых для обновления BIOS", + "Угроза конфликта юрисдикций различных стран", + "Угроза межсайтового скриптинга", + "Угроза межсайтовой подделки запроса", + "Угроза нарушения доступности облачного сервера", + "Угроза нарушения изоляции пользовательских данных внутри виртуальной машины", + "Угроза нарушения изоляции среды исполнения BIOS", + "Угроза нарушения процедуры аутентификации субъектов виртуального информационного взаимодействия", + "Угроза нарушения работоспособности грид-системы при нетипичной сетевой нагрузке", + "Угроза нарушения технологии обработки информации путём несанкционированного внесения изменений в образы виртуальных машин", + "Угроза нарушения целостности данных кеша", + "Угроза неверного определения формата входных данных, поступающих в хранилище больших данных", + "Угроза невозможности восстановления сессии работы на ПЭВМ при выводе из промежуточных состояний питания", + "Угроза невозможности миграции образов виртуальных машин из-за несовместимости аппаратного и программного обеспечения", + "Угроза невозможности управления правами пользователей BIOS", + "Угроза недобросовестного исполнения обязательств поставщиками облачных услуг", + "Угроза незащищённого администрирования облачных услуг", + "Угроза некачественного переноса инфраструктуры в облако", + "Угроза неконтролируемого копирования данных внутри хранилища больших данных", + "Угроза неконтролируемого роста числа виртуальных машин", + "Угроза неконтролируемого роста числа зарезервированных вычислительных ресурсов", + "Угроза неконтролируемого уничтожения информации хранилищем больших данных", + "Угроза некорректного задания структуры данных транзакции", + "Угроза некорректного использования прозрачного прокси-сервера за счёт плагинов браузера", + "Угроза некорректного использования функционала программного и аппаратного обеспечения", + "Угроза некорректной реализации политики лицензирования в облаке", + "Угроза неопределённости в распределении ответственности между ролями в облаке", + "Угроза неопределённости ответственности за обеспечение безопасности облака", + "Угроза неправомерного ознакомления с защищаемой информацией", + "Угроза неправомерного/некорректного использования интерфейса взаимодействия с приложением", + "Угроза неправомерных действий в каналах связи", + "Угроза непрерывной модернизации облачной инфраструктуры", + "Угроза несанкционированного восстановления удалённой защищаемой информации", + "Угроза несанкционированного выключения или обхода механизма защиты от записи в BIOS", + "Угроза несанкционированного доступа к активному и (или) пассивному виртуальному и (или) физическому сетевому оборудованию из физической и (или) виртуальной сети", + "Угроза несанкционированного доступа к аутентификационной информации", + "Угроза несанкционированного доступа к виртуальным каналам передачи", + "Угроза несанкционированного доступа к гипервизору из виртуальной машины и (или) физической сети", + "Угроза несанкционированного доступа к данным за пределами зарезервированного адресного пространства, в том числе выделенного под виртуальное аппаратное обеспечение", + "Угроза несанкционированного доступа к защищаемым виртуальным машинам из виртуальной и (или) физической сети", + "Угроза несанкционированного доступа к защищаемым виртуальным машинам со стороны других виртуальных машин", + "Угроза несанкционированного доступа к защищаемым виртуальным устройствам из виртуальной и (или) физической сети", + "Угроза несанкционированного доступа к локальному компьютеру через клиента грид-системы", + "Угроза несанкционированного доступа к сегментам вычислительного поля", + "Угроза несанкционированного доступа к системе по беспроводным каналам", + "Угроза несанкционированного доступа к системе хранения данных из виртуальной и (или) физической сети", + "Угроза несанкционированного доступа к хранимой в виртуальном пространстве защищаемой информации", + "Угроза несанкционированного изменения аутентификационной информации", + "Угроза несанкционированного использования привилегированных функций BIOS", + "Угроза несанкционированного копирования защищаемой информации", + "Угроза несанкционированного редактирования реестра", + "Угроза несанкционированного создания учётной записи пользователя", + "Угроза несанкционированного удаления защищаемой информации", + "Угроза несанкционированного удалённого внеполосного доступа к аппаратным средствам", + "Угроза несанкционированного управления буфером", + "Угроза несанкционированного управления синхронизацией и состоянием", + "Угроза несанкционированного управления указателями", + "Угроза несогласованности политик безопасности элементов облачной инфраструктуры", + "Угроза несогласованности правил доступа к большим данным", + "Угроза обнаружения открытых портов и идентификации привязанных к ним сетевых служб", + "Угроза обнаружения хостов", + "Угроза обхода некорректно настроенных механизмов аутентификации", + "Угроза общедоступности облачной инфраструктуры", + "Угроза опосредованного управления группой программ через совместно используемые данные", + "Угроза определения типов объектов защиты", + "Угроза определения топологии вычислительной сети", + "Угроза отказа в загрузке входных данных неизвестного формата хранилищем больших данных", + "Угроза отказа в обслуживании системой хранения данных суперкомпьютера", + "Угроза отключения контрольных датчиков", + "Угроза ошибки обновления гипервизора", + "Угроза перебора всех настроек и параметров приложения", + "Угроза перегрузки грид-системы вычислительными заданиями", + "Угроза передачи данных по скрытым каналам", + "Угроза передачи запрещённых команд на оборудование с числовым программным управлением", + "Угроза перезагрузки аппаратных и программно-аппаратных средств вычислительной техники", + "Угроза переполнения целочисленных переменных", + "Угроза перехвата вводимой и выводимой на периферийные устройства информации", + "Угроза перехвата данных, передаваемых по вычислительной сети", + "Угроза перехвата привилегированного потока", + "Угроза перехвата привилегированного процесса", + "Угроза перехвата управления гипервизором", + "Угроза перехвата управления средой виртуализации", + "Угроза повреждения системного реестра", + "Угроза повышения привилегий", + "Угроза подбора пароля BIOS", + "Угроза подделки записей журнала регистрации событий", + "Угроза подключения к беспроводной сети в обход процедуры аутентификации", + "Угроза подмены беспроводного клиента или точки доступа", + "Угроза подмены действия пользователя путём обмана", + "Угроза подмены доверенного пользователя", + "Угроза подмены резервной копии программного обеспечения BIOS", + "Угроза подмены содержимого сетевых ресурсов", + "Угроза подмены субъекта сетевого доступа", + "Угроза получения предварительной информации об объекте защиты", + "Угроза получения сведений о владельце беспроводного устройства", + "Угроза потери доверия к поставщику облачных услуг", + "Угроза потери и утечки данных, обрабатываемых в облаке", + "Угроза потери информации вследствие несогласованности работы узлов хранилища больших данных", + "Угроза потери управления облачными ресурсами", + "Угроза потери управления собственной инфраструктурой при переносе её в облако", + "Угроза преодоления физической защиты", + "Угроза приведения системы в состояние «отказ в обслуживании»", + "Угроза привязки к поставщику облачных услуг", + "Угроза приостановки оказания облачных услуг вследствие технических сбоев", + "Угроза программного выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации", + "Угроза программного сброса пароля BIOS", + "Угроза пропуска проверки целостности программного обеспечения", + "Угроза прямого обращения к памяти вычислительного поля суперкомпьютера", + "Угроза распространения несанкционированно повышенных прав на всю грид-систему", + "Угроза сбоя автоматического управления системой разграничения доступа хранилища больших данных", + "Угроза сбоя обработки специальным образом изменённых файлов", + "Угроза сбоя процесса обновления BIOS", + "Угроза сканирования веб-сервисов, разработанных на основе языка описания WSDL", + "Угроза удаления аутентификационной информации", + "Угроза усиления воздействия на вычислительные ресурсы пользователей при помощи сторонних серверов", + "Угроза установки уязвимых версий обновления программного обеспечения BIOS", + "Угроза утраты вычислительных ресурсов", + "Угроза утраты носителей информации", + "Угроза физического выведения из строя средств хранения, обработки и (или) ввода/вывода/передачи информации", + "Угроза форматирования носителей информации", + "Угроза «форсированного веб-браузинга»", + "Угроза хищения средств хранения, обработки и (или) ввода/вывода/передачи информации", + "Угроза чрезмерного использования вычислительных ресурсов суперкомпьютера в ходе интенсивного обмена межпроцессорными сообщениями", + "Угроза эксплуатации цифровой подписи программного кода", + "Угроза перехвата исключения/сигнала из привилегированного блока функций", + "Угроза распространения состояния «отказ в обслуживании» в облачной инфраструктуре", + "Угроза включения в проект не достоверно испытанных компонентов", + "Угроза внедрения системной избыточности", + "Угроза заражения компьютера при посещении неблагонадёжных сайтов", + "Угроза «кражи» учётной записи доступа к сетевым сервисам", + "Угроза наличия механизмов разработчика", + "Угроза неправомерного шифрования информации", + "Угроза скрытного включения вычислительного устройства в состав бот-сети", + "Угроза распространения «почтовых червей»", + "Угроза «спама» веб-сервера", + "Угроза «фарминга»", + "Угроза «фишинга»", + "Угроза нарушения технологического/производственного процесса из-за временны́х задержек, вносимых средством защиты", + "Угроза неподтверждённого ввода данных оператором в систему, связанную с безопасностью", + "Угроза несанкционированного использования системных и сетевых утилит", + "Угроза несанкционированной модификации защищаемой информации", + "Угроза отказа подсистемы обеспечения температурного режима", + "Угроза перехвата одноразовых паролей в режиме реального времени", + "Угроза физического устаревания аппаратных компонентов", + "Угроза перехвата управления автоматизированной системой управления технологическими процессами", + "Угроза агрегирования данных, обрабатываемых с помощью мобильного устройства", + "Угроза несанкционированного изменения параметров настройки средств защиты информации", + "Угроза внедрения вредоносного кода через рекламу, сервисы и контент", + "Угроза несанкционированного воздействия на средство защиты информации", + "Угроза подмены программного обеспечения", + "Угроза маскирования действий вредоносного кода", + "Угроза внедрения вредоносного кода за счет посещения зараженных сайтов в сети Интернет", + "Угроза внедрения вредоносного кода в дистрибутив программного обеспечения", + "Угроза использования уязвимых версий программного обеспечения", + "Угроза утечки информации за счет применения вредоносным программным обеспечением алгоритмов шифрования трафика", + "Угроза несанкционированного использования привилегированных функций мобильного устройства", + "Угроза удаленного запуска вредоносного кода в обход механизмов защиты операционной системы", + "Угроза контроля вредоносной программой списка приложений, запущенных на мобильном устройстве", + "Угроза хищения аутентификационной информации из временных файлов cookie", + "Угроза скрытной регистрации вредоносной программой учетных записей администраторов", + "Угроза перехвата управления мобильного устройства при использовании виртуальных голосовых ассистентов", + "Угроза хищения информации с мобильного устройства при использовании виртуальных голосовых ассистентов", + "Угроза утечки пользовательских данных при использовании функций автоматического заполнения аутентификационной информации в браузере", + "Угроза несанкционированной установки приложений на мобильные устройства", + "Угроза утечки информации с неподключенных к сети Интернет компьютеров", + "Угроза несанкционированного изменения вредоносной программой значений параметров программируемых логических контроллеров", + "Угроза нарушения работы компьютера и блокирования доступа к его данным из-за некорректной работы установленных на нем средств защиты", + "Угроза отказа в работе оборудования из-за изменения геолокационной информации о нем", + "Угроза несанкционированного доступа к параметрам настройки оборудования за счет использования «мастер-кодов» (инженерных паролей)", + "Угроза нецелевого использования вычислительных ресурсов средства вычислительной техники", + "Угроза несанкционированного доступа к защищаемой памяти \r\nядра процессора", + "Угроза нарушения работы информационной системы, вызванного обновлением используемого в ней программного обеспечения", + "Угроза использования непроверенных пользовательских данных при формировании конфигурационного файла, используемого программным обеспечением администрирования информационных систем", + "Угроза перехвата управления информационной системой", + "Угроза обхода многофакторной аутентификации", + "Угроза несвоевременного выявления и реагирования компонентами информационной (автоматизированной) системы (в том числе средствами защиты информации) на события безопасности информации", + "Угроза несанкционированного доступа к системе при помощи сторонних сервисов", + "Угроза получения несанкционированного доступа к приложениям, установленным на Smart-картах", + "Угроза использования скомпрометированного доверенного источника обновлений программного обеспечения", + "Угроза раскрытия информации о модели машинного обучения", + "Угроза хищения обучающих данных", + "Угроза нарушения функционирования («обхода») средств, реализующих технологии искусственного интеллекта", + "Угроза модификации модели машинного обучения путем искажения («отравления») обучающих данных", + "Угроза подмены модели машинного обучения", + "Угроза несанкционированного доступа к контейнерам, предоставляющего пользователям расширенные привилегии", + "Угроза нарушения целостности (подмены) контейнеров", + "Угроза нарушения изоляции контейнеров", + "Угроза внедрения вредоносного программного обеспечения в контейнеры", + "Угроза модификации (подмены) образов контейнеров" +] \ No newline at end of file diff --git a/dokogen/dictionaries/threats_fstec_date.txt b/dokogen/dictionaries/threats_fstec_date.txt new file mode 100644 index 0000000..ed514eb --- /dev/null +++ b/dokogen/dictionaries/threats_fstec_date.txt @@ -0,0 +1 @@ +2026-08-04 \ No newline at end of file diff --git a/dokogen/fstec_threats.py b/dokogen/fstec_threats.py new file mode 100644 index 0000000..2b39a70 --- /dev/null +++ b/dokogen/fstec_threats.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +"""DokoGen — загрузка перечня угроз безопасности информации с сайта ФСТЭК (БДУ). + +Скачивает https://bdu.fstec.ru/threat (таблица угроз, с пагинацией), +парсит названия угроз и сохраняет в dictionaries/threats_fstec.json. +Рядом хранится дата актуализации (threats_fstec_date.txt). +""" + +import os +import re +import json +import ssl +import urllib.request +import urllib.parse +from datetime import datetime + +BDU_URL = 'https://bdu.fstec.ru/threat' +USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' + + +def _ssl_ctx(): + """SSL-контекст: сначала обычная проверка, при проблемах — без проверки.""" + try: + ctx = ssl.create_default_context() + return ctx + except Exception: + pass + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + return ctx + + +def _fetch(url, timeout=30): + """GET с User-Agent, возвращает текст или None.""" + req = urllib.request.Request(url, headers={'User-Agent': USER_AGENT}) + try: + with urllib.request.urlopen(req, timeout=timeout, context=_ssl_ctx()) as resp: + return resp.read().decode('utf-8', errors='ignore') + except Exception: + # повтор с отключённой проверкой сертификата (некоторые сети/прокси) + try: + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp: + return resp.read().decode('utf-8', errors='ignore') + except Exception: + return None + + +def _parse_threats(html): + """Извлекает названия угроз из HTML-таблицы БДУ. + + Строки вида: ...УБИ. 001Угроза ...... + Возвращает список названий (без кода). + """ + names = [] + if not html: + return names + for row in re.findall(r']*>(.*?)', html, re.S): + cells = re.findall(r']*>(.*?)', row, re.S) + cleaned = [re.sub(r'<[^>]+>', '', c).strip() for c in cells] + # формат: [код, название, ...] — берём ячейку, которая не похожа на код УБИ + for cell in cleaned: + if not cell: + continue + if re.fullmatch(r'УБИ\.?\s*[\d.]+', cell): + continue + if cell in ('№', 'Идентификатор', 'Наименование', 'Дата', 'Уровень опасности'): + continue + names.append(cell) + break + return names + + +def download_fstec_threats(dict_dir=None, max_pages=8, page_size=100): + """Скачивает перечень угроз с bdu.fstec.ru. + + Возвращает (ok, сообщение, список_угроз). При успехе сохраняет: + - dictionaries/threats_fstec.json — список названий угроз + - dictionaries/threats_fstec_date.txt — дата актуализации (ГГГГ-ММ-ДД) + """ + if dict_dir is None: + dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries') + + threats = [] + for page in range(1, max_pages + 1): + url = f"{BDU_URL}?size={page_size}&page={page}" + html = _fetch(url) + if not html: + break + names = _parse_threats(html) + if not names: + break + for n in names: + if n not in threats: + threats.append(n) + # если на странице меньше page_size — это последняя + if len(names) < page_size: + break + + if not threats: + return False, "Не удалось загрузить угрозы с сайта ФСТЭК (bdu.fstec.ru)", [] + + try: + os.makedirs(dict_dir, exist_ok=True) + with open(os.path.join(dict_dir, 'threats_fstec.json'), 'w', encoding='utf-8') as f: + json.dump(threats, f, ensure_ascii=False, indent=2) + today = datetime.now().strftime('%Y-%m-%d') + with open(os.path.join(dict_dir, 'threats_fstec_date.txt'), 'w', encoding='utf-8') as f: + f.write(today) + except Exception as e: + return False, f"Ошибка сохранения: {e}", threats + + return True, f"Загружено угроз: {len(threats)}", threats + + +def get_threats_date(dict_dir=None): + """Дата последней актуализации списка угроз (или None).""" + if dict_dir is None: + dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries') + path = os.path.join(dict_dir, 'threats_fstec_date.txt') + if os.path.exists(path): + try: + with open(path, encoding='utf-8') as f: + return f.read().strip() + except Exception: + pass + return None + + +def load_threats(dict_dir=None): + """Загружает список угроз: сначала скачанный с ФСТЭК, иначе базовый threats.json.""" + if dict_dir is None: + dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries') + fstec = os.path.join(dict_dir, 'threats_fstec.json') + if os.path.exists(fstec): + try: + with open(fstec, encoding='utf-8') as f: + return json.load(f) + except Exception: + pass + base = os.path.join(dict_dir, 'threats.json') + try: + with open(base, encoding='utf-8') as f: + return json.load(f) + except Exception: + return [] diff --git a/dokogen/security_tab_187_reference.py b/dokogen/security_tab_187_reference.py new file mode 100644 index 0000000..77b1339 --- /dev/null +++ b/dokogen/security_tab_187_reference.py @@ -0,0 +1,480 @@ +# -*- coding: utf-8 -*- +""" +ВКЛАДКА «БЕЗОПАСНОСТЬ» из DokoGen 187-ФЗ (dokogen187, ветка source) +===================================================================== +Скопировано для переноса в DokoGen MU, чтобы интерфейс был одинаковым. + +ЧТО ВХОДИТ: + 1. Секция построения вкладки (вставить в метод _build_is_tab перед вкладкой «Средства защиты») + 2. Методы-обработчики (вставить в класс приложения) + 3. Словари — скопированы в dokogen/dictionaries/: + attacker_categories.json, incidents.json, criteria_indicators.json, + org_measures.json, tech_measures.json, threats.json, + threats_fstec.json, threats_fstec_date.txt + 4. Модуль fstec_threats.py (скачивание перечня угроз с bdu.fstec.ru) + 5. check_security_ai() — ИИ-проверка (см. конец файла, добавить в verify.py) + +ЗАВИСИМОСТИ: + - tkinter/ttk (стандарт) + - dokogen/fstec_threats.py (загрузка угроз ФСТЭК) + - dokogen/verify.py: check_security_ai (ИИ-проверка) + - dokogen/api_settings.py: load_api_settings + - Поля модели InformationSystem (добавить в models.py, если нет): + attacker_category, attacker_characteristics, threats, incidents, + criteria_values, criteria_justification, org_measures, tech_measures + - Маппинги _collect_is/_on_is_select (см. комментарии в конце) + +ПРИМЕЧАНИЕ: в DokoGen MU (152-ФЗ) модель InformationSystem другая — + при переносе адаптировать имена полей или добавить новые. +""" + + +# ============================================================ +# 1. СЕКЦИЯ ПОСТРОЕНИЯ ВКЛАДКИ (вставить в _build_is_tab) +# ============================================================ + + # ===== Вкладка «Безопасность» ===== + sec_tab = ttk.Frame(nb2) + nb2.add(sec_tab, text="Безопасность") + sec_frame = ttk.Frame(sec_tab, padding=8) + sec_frame.pack(fill=tk.BOTH, expand=True) + + # --- Категория нарушителя (словарик) --- + ttk.Label(sec_frame, text="Категория нарушителя:").grid(row=0, column=0, sticky=tk.W, pady=2) + self.is_attacker_category_combo = ttk.Combobox(sec_frame, width=68, state='readonly') + self.is_attacker_category_combo.grid(row=0, column=1, padx=8, sticky=tk.EW, pady=1) + try: + import json as _json + _ac = os.path.join(os.path.dirname(__file__), 'dictionaries', 'attacker_categories.json') + with open(_ac, encoding='utf-8') as _f: + self.is_attacker_category_combo['values'] = [''] + _json.load(_f) + except Exception: + pass + + # --- Характеристика нарушителя --- + ttk.Label(sec_frame, text="Характеристика нарушителя:").grid(row=1, column=0, sticky=tk.NW, pady=2) + self.is_attacker_characteristics_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_attacker_characteristics_listbox.grid(row=1, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_char_btn = ttk.Frame(sec_frame) + sec_char_btn.grid(row=1, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_char_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_char_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_char_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_attacker_characteristics_listbox)).pack(fill=tk.X, pady=1) + + # --- Основные угрозы БИ (словарик + загрузка с ФСТЭК) --- + ttk.Label(sec_frame, text="Основные угрозы БИ:").grid(row=2, column=0, sticky=tk.W, pady=2) + self.is_threats_listbox = tk.Listbox(sec_frame, height=5, exportselection=False) + self.is_threats_listbox.grid(row=2, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_thr_btn = ttk.Frame(sec_frame) + sec_thr_btn.grid(row=2, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_thr_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_threats_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_threats_listbox, 'threats_fstec.json')).pack(fill=tk.X, pady=1) + ttk.Button(sec_thr_btn, text="⬇️ Загрузить угрозы ФСТЭК", + command=self._download_fstec_threats).pack(fill=tk.X, pady=1) + self._threats_date_label = ttk.Label(sec_frame, text="", foreground='gray') + self._threats_date_label.grid(row=3, column=1, sticky=tk.W, padx=8, pady=(0, 2)) + + # --- Типы компьютерных инцидентов (словарик) --- + ttk.Label(sec_frame, text="Типы компьютерных инцидентов:").grid(row=4, column=0, sticky=tk.W, pady=2) + self.is_incidents_listbox = tk.Listbox(sec_frame, height=4, exportselection=False) + self.is_incidents_listbox.grid(row=4, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_inc_btn = ttk.Frame(sec_frame) + sec_inc_btn.grid(row=4, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_inc_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_inc_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_inc_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_incidents_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_inc_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_incidents_listbox, 'incidents.json')).pack(fill=tk.X, pady=1) + + # --- Показатели критериев значимости (словарик) --- + ttk.Label(sec_frame, text="Показатели критериев значимости:").grid(row=5, column=0, sticky=tk.W, pady=2) + self.is_criteria_values_listbox = tk.Listbox(sec_frame, height=4, exportselection=False) + self.is_criteria_values_listbox.grid(row=5, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_crit_btn = ttk.Frame(sec_frame) + sec_crit_btn.grid(row=5, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_crit_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_crit_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_crit_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_criteria_values_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_crit_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_criteria_values_listbox, 'criteria_indicators.json')).pack(fill=tk.X, pady=1) + + # --- Обоснование значений показателей --- + ttk.Label(sec_frame, text="Обоснование значений показателей:").grid(row=6, column=0, sticky=tk.NW, pady=2) + self.is_criteria_justification_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_criteria_justification_listbox.grid(row=6, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_just_btn = ttk.Frame(sec_frame) + sec_just_btn.grid(row=6, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_just_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_just_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_just_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_criteria_justification_listbox)).pack(fill=tk.X, pady=1) + + # --- Организационные меры (словарик) --- + ttk.Label(sec_frame, text="Организационные меры:").grid(row=7, column=0, sticky=tk.W, pady=2) + self.is_org_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_org_measures_listbox.grid(row=7, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_org_btn = ttk.Frame(sec_frame) + sec_org_btn.grid(row=7, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_org_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_org_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_org_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_org_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_org_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_org_measures_listbox, 'org_measures.json')).pack(fill=tk.X, pady=1) + + # --- Технические меры (словарик) --- + ttk.Label(sec_frame, text="Технические меры:").grid(row=8, column=0, sticky=tk.W, pady=2) + self.is_tech_measures_listbox = tk.Listbox(sec_frame, height=3, exportselection=False) + self.is_tech_measures_listbox.grid(row=8, column=1, sticky=tk.EW, padx=(8, 4), pady=1) + sec_tech_btn = ttk.Frame(sec_frame) + sec_tech_btn.grid(row=8, column=2, sticky=tk.N, padx=(4, 0)) + ttk.Button(sec_tech_btn, text="+ Добавить", + command=lambda: self._add_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_tech_btn, text="- Удалить", + command=lambda: self._del_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_tech_btn, text="✏️ Редактировать", + command=lambda: self._edit_sw_item(self.is_tech_measures_listbox)).pack(fill=tk.X, pady=1) + ttk.Button(sec_tech_btn, text="📚 Из словаря", + command=lambda: self._select_sw_from_dict(self.is_tech_measures_listbox, 'tech_measures.json')).pack(fill=tk.X, pady=1) + + # --- Кнопка ИИ-проверки --- + ttk.Button(sec_frame, text="🤖 Проверить безопасность через ИИ", + command=self._check_security_ai).grid( + row=9, column=0, columnspan=3, sticky=tk.W, pady=(10, 0)) + + sec_frame.columnconfigure(1, weight=1) + + + +# ============================================================ +# 2. МЕТОДЫ-ОБРАБОТЧИКИ (вставить в класс приложения) +# ============================================================ + + def _download_fstec_threats(self): + """Скачать перечень угроз с сайта ФСТЭК (БДУ) и обновить список.""" + try: + from .fstec_threats import download_fstec_threats, get_threats_date + except ImportError as e: + messagebox.showerror("Ошибка", f"Модуль fstec_threats не найден: {e}") + return + + self.log("⬇️ Загрузка перечня угроз с bdu.fstec.ru…") + ok, msg, threats = download_fstec_threats() + if not ok: + messagebox.showerror("Загрузка угроз", + f"Не удалось загрузить:\n{msg}\n\n" + "Проверьте доступ к интернету или попробуйте позже.") + self.log(f"❌ Загрузка угроз: {msg}") + return + + # Обновляем label даты актуализации + self._update_threats_date_label() + # Если список угроз уже открыт — можно показать диалог выбора + messagebox.showinfo("Загрузка угроз", + f"{msg}\n\nСписок сохранён. Теперь нажмите «📚 Из словаря» " + "в разделе «Основные угрозы БИ», чтобы выбрать нужные угрозы.") + self.log(f"✅ {msg}") + + def _update_threats_date_label(self): + """Обновить подпись даты актуализации списка угроз.""" + try: + from .fstec_threats import get_threats_date + d = get_threats_date() + except Exception: + d = None + if hasattr(self, '_threats_date_label'): + if d: + self._threats_date_label.config( + text=f"Актуально на: {d}", foreground='#2e7d32') + else: + self._threats_date_label.config( + text="⚠ Список угроз ещё не скачан — нажмите «⬇️ Загрузить угрозы ФСТЭК»", + foreground='#c62828') + + def _check_security_ai(self): + """Проверка вкладки «Безопасность» через ИИ (DeepSeek/OpenAI-совместимый API).""" + if self._current_is_idx is None: + messagebox.showinfo("Безопасность", "Сначала выберите объект КИИ в списке слева") + return + settings = load_api_settings() + if not settings.get('ai_api_key'): + messagebox.showwarning("Настройки", "Не задан API-ключ ИИ.\nМеню «Файл → Настройки»") + return + + # Собираем текущие данные безопасности объекта + self._collect_is() + isys = self.model.information_systems[self._current_is_idx] + security_data = { + 'name': isys.name or '', + 'description': isys.description or '', + 'category': isys.category or '', + 'sphere': isys.sphere or '', + 'object_type': isys.object_type or '', + 'architecture': isys.architecture or '', + 'attacker_category': isys.attacker_category or '', + 'attacker_characteristics': isys.attacker_characteristics or '', + 'threats': isys.threats or '', + 'incidents': isys.incidents or '', + 'criteria_values': isys.criteria_values or '', + 'criteria_justification': isys.criteria_justification or '', + 'org_measures': isys.org_measures or '', + 'tech_measures': isys.tech_measures or '', + } + self.log(f"🤖 Запрос к ИИ ({settings.get('ai_model') or 'deepseek-chat'}): проверка безопасности") + self._verify_log("Проверка безопасности через ИИ…") + + def _run(): + try: + from .verify import check_security_ai + result, err = check_security_ai( + security_data, + settings['ai_api_key'], + settings.get('ai_model'), + settings.get('ai_base_url'), + ) + self.root.after(0, lambda: self._security_ai_done(result, err)) + except Exception as e: + self.root.after(0, lambda: self._security_ai_done(None, str(e))) + + threading.Thread(target=_run, daemon=True).start() + + def _security_ai_done(self, result, err): + """Обработка ответа ИИ по безопасности.""" + if err: + self._verify_log(f"❌ {err}") + self.log(f"❌ Проверка безопасности: {err}") + return + d = result['data'] + lines = [f"Источник: {result['source']}"] + lines.append(f"Оценка: {d.get('rating', 'warning')}") + notes = d.get('notes') or [] + if notes: + lines.append("\nЗамечания:") + for n in notes: + lines.append(f" • {n}") + props = d.get('proposals') or {} + if any(props.values()): + lines.append("\nПредложения ИИ:") + for key, label in [('attacker_category', 'Нарушитель'), + ('threats', 'Угрозы'), + ('incidents', 'Инциденты'), + ('criteria_values', 'Показатели'), + ('criteria_justification', 'Обоснование'), + ('org_measures', 'Орг. меры'), + ('tech_measures', 'Тех. меры')]: + v = props.get(key) + if v: + lines.append(f" {label}: {v}") + self._verify_log('\n'.join(lines)) + self.log(f"🤖 ИИ: оценка {d.get('rating', 'warning')}, замечаний: {len(notes)}") + + def _refine_dict_item_dialog(self, base_item): + """Окно уточнения модели и количества для позиции из словаря. + + Возвращает полную строку вида «Windows 10 Pro 5 шт.» или + «Сервер Supermicro SSG-2027R -E1R24N 2 шт.» или None. + """ + import re + # Убираем из базы маркер «(уточнить …)» + base = re.sub(r'\s*\([^)]*уточн[^)]*\)', '', base_item, flags=re.I).strip() or base_item + + dialog = tk.Toplevel(self.root) + dialog.title("Уточнить модель и количество") + dialog.geometry("500x180") + dialog.transient(self.root) + dialog.grab_set() + + frame = ttk.Frame(dialog, padding=10) + frame.pack(fill=tk.BOTH, expand=True) + + ttk.Label(frame, text=f"Позиция: {base}", wraplength=470, + font=('Arial', 9, 'bold')).pack(anchor=tk.W, pady=(0, 8)) + + ttk.Label(frame, text="Модель / версия / конфигурация (необязательно):").pack(anchor=tk.W) + model_entry = ttk.Entry(frame, width=60) + model_entry.pack(fill=tk.X, pady=(2, 6)) + model_entry.focus_set() + + ttk.Label(frame, text="Количество:").pack(anchor=tk.W) + count_entry = ttk.Entry(frame, width=20) + count_entry.pack(anchor=tk.W, pady=(2, 6)) + + result = {} + + def _ok(): + model = model_entry.get().strip() + count = count_entry.get().strip() + text = base + if model: + text = f"{text} {model}" + if count: + text = f"{text} {count} шт." + result['val'] = text + dialog.destroy() + + def _cancel(): + result['val'] = None + dialog.destroy() + + btn_frame = ttk.Frame(frame) + btn_frame.pack(fill=tk.X, pady=(6, 0)) + ttk.Button(btn_frame, text="✅ ОК", command=_ok).pack(side=tk.RIGHT, padx=2) + ttk.Button(btn_frame, text="Отмена", command=_cancel).pack(side=tk.RIGHT, padx=2) + + dialog.bind('', lambda e: _ok()) + dialog.bind('', lambda e: _cancel()) + dialog.wait_window() + return result.get('val') + + def _select_sw_from_dict(self, listbox, dict_name): + """Выбор позиций из словаря (JSON) — можно несколько.""" + import json + dict_path = os.path.join(os.path.dirname(__file__), 'dictionaries', dict_name) + try: + with open(dict_path, 'r', encoding='utf-8') as f: + items = json.load(f) + title = "Выбор из словаря" + self._select_from_dialog(title, items, listbox) + except Exception as e: + messagebox.showerror("Ошибка", f"Не удалось загрузить словарь: {e}") + + def _sw_dialog(self, title, initial=""): + """Большое окно для ввода/редактирования позиции ПО.""" + dialog = tk.Toplevel(self.root) + dialog.title(title) + dialog.geometry("560x220") + dialog.transient(self.root) + dialog.grab_set() + dialog.resizable(True, True) + + frame = ttk.Frame(dialog, padding=10) + frame.pack(fill=tk.BOTH, expand=True) + + ttk.Label(frame, text="Текст позиции:").pack(anchor=tk.W) + txt = tk.Text(frame, width=70, height=6, wrap=tk.WORD) + txt.pack(fill=tk.BOTH, expand=True, pady=5) + txt.insert('1.0', initial) + txt.focus_set() + + result = {} + + def _ok(): + result['val'] = txt.get('1.0', 'end-1c').strip() + dialog.destroy() + + def _cancel(): + result['val'] = None + dialog.destroy() + + btn_frame = ttk.Frame(frame) + btn_frame.pack(fill=tk.X) + ttk.Button(btn_frame, text="✅ ОК", command=_ok).pack(side=tk.RIGHT, padx=2) + ttk.Button(btn_frame, text="Отмена", command=_cancel).pack(side=tk.RIGHT, padx=2) + + dialog.bind('', lambda e: _ok()) + dialog.bind('', lambda e: _cancel()) + dialog.wait_window() + return result.get('val') + + def _add_sw_item(self, listbox): + """Добавить строку в список ПО (большое окно).""" + item = self._sw_dialog("Добавить позицию") + if item: + listbox.insert(tk.END, item) + + def _del_sw_item(self, listbox): + """Удалить выбранную строку из списка ПО.""" + sel = listbox.curselection() + if sel: + listbox.delete(sel[0]) + + def _edit_sw_item(self, listbox): + """Редактировать выбранную строку списка ПО (большое окно).""" + sel = listbox.curselection() + if not sel: + messagebox.showinfo("Редактировать", "Сначала выберите позицию в списке") + return + current = listbox.get(sel[0]) + item = self._sw_dialog("Редактировать позицию", initial=current) + if item is not None: + listbox.delete(sel[0]) + listbox.insert(sel[0], item) + +# ============================================================ +# 3. ИИ-ПРОВЕРКА (добавить в verify.py) +# ============================================================ + +def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None): + """ИИ-проверка заполненной вкладки «Безопасность» объекта КИИ (236 приказ). + + Возвращает (результат, ошибка): результат — dict с 'оценка', 'замечания', 'предложения'. + """ + if not api_key: + return None, "Не задан API-ключ ИИ (Файл → Настройки → API)" + system = ( + "Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, " + "приказ ФСТЭК России от 25.12.2017 № 239 «Об утверждении Требований по обеспечению " + "безопасности значимых объектов КИИ» и приказ № 236). Проверь заполненные сведения " + "об объекте КИИ и дай рекомендации. Верни ТОЛЬКО JSON: " + '{"оценка": "ok|warning|error", ' + '"замечания": ["..."], ' + '"предложения": {"attacker_category": "...", "threats": "...", "incidents": "...", ' + '"criteria_values": "...", "criteria_justification": "...", ' + '"org_measures": "...", "tech_measures": "..."}} ' + "В «предложениях» укажи, что добавить/исправить по каждому разделу, если нужно, иначе пустые строки." + ) + user = json.dumps(security_data, ensure_ascii=False) + js, err = _ai_json(api_key, model, base_url, system, user) + if err: + return None, err + notes = js.get('замечания') or [] + if isinstance(notes, str): + notes = [notes] + proposals = js.get('предложения') or {} + if not isinstance(proposals, dict): + proposals = {} + return { + 'source': 'ИИ', + 'source_url': 'openai-совместимый API', + 'disclaimer': AI_DISCLAIMER, + 'kind': 'security', + 'data': { + 'rating': js.get('оценка', 'warning'), + 'notes': notes, + 'proposals': proposals, + }, + }, None + +# ============================================================ +# 4. МАППИНГИ ПОЛЕЙ +# ============================================================ +# В _collect_is() (список виджетов → модель): +# ('is_attacker_category_combo', 'attacker_category'), +# ('is_attacker_characteristics_listbox', 'attacker_characteristics'), +# ('is_threats_listbox', 'threats'), +# ('is_incidents_listbox', 'incidents'), +# ('is_criteria_values_listbox', 'criteria_values'), +# ('is_criteria_justification_listbox', 'criteria_justification'), +# ('is_org_measures_listbox', 'org_measures'), +# ('is_tech_measures_listbox', 'tech_measures'), +# В _on_is_select() — обратный порядок (combo → .set(), listbox → split('\n')) +# После построения вкладок вызвать: self._update_threats_date_label() diff --git a/dokogen/verify.py b/dokogen/verify.py index f562aa2..afea6f6 100644 --- a/dokogen/verify.py +++ b/dokogen/verify.py @@ -389,6 +389,45 @@ def compare_company_values(old_data, new_data): return diffs +def check_security_ai(security_data, api_key, model='deepseek-chat', base_url=None): + """ИИ-проверка заполненной вкладки «Безопасность» объекта КИИ (236 приказ).""" + if not api_key: + return None, "Не задан API-ключ ИИ (Файл → Настройки → API)" + system = ( + "Ты — эксперт по безопасности критической информационной инфраструктуры (187-ФЗ, " + "приказ ФСТЭК России от 25.12.2017 № 239 «Об утверждении Требований по обеспечению " + "безопасности значимых объектов КИИ» и приказ № 236). Проверь заполненные сведения " + "об объекте КИИ и дай рекомендации. Верни ТОЛЬКО JSON: " + '{"оценка": "ok|warning|error", ' + '"замечания": ["..."], ' + '"предложения": {"attacker_category": "...", "threats": "...", "incidents": "...", ' + '"criteria_values": "...", "criteria_justification": "...", ' + '"org_measures": "...", "tech_measures": "..."}} ' + "В «предложениях» укажи, что добавить/исправить по каждому разделу, если нужно, иначе пустые строки." + ) + user = json.dumps(security_data, ensure_ascii=False) + js, err = _ai_json(api_key, model, base_url, system, user) + if err: + return None, err + notes = js.get('замечания') or [] + if isinstance(notes, str): + notes = [notes] + proposals = js.get('предложения') or {} + if not isinstance(proposals, dict): + proposals = {} + return { + 'source': 'ИИ', + 'source_url': 'openai-совместимый API', + 'disclaimer': AI_DISCLAIMER, + 'kind': 'security', + 'data': { + 'rating': js.get('оценка', 'warning'), + 'notes': notes, + 'proposals': proposals, + }, + }, None + + def check_company_ai(company_data, api_key, model='deepseek-chat', base_url=None): """ИИ-проверка реквизитов организации.