«Десериализация ИИ» звучит так, будто нейросеть разобрали на атомы, пересобрали обратно и она в процессе обрела злую душу. Души там нет. Сознания тоже. Есть файл модели, загрузчик и старый инженерный грех: программа берет недоверенные байты и превращает их в объект в памяти. Если формат разрешает хранить не только числа, но и инструкции для создания объектов, загрузка модели может выполнить код.
Точнее говорить не «десериализация ИИ», а небезопасная десериализация артефактов машинного обучения. Под артефактами понимают веса модели, чекпойнты, токенизаторы, препроцессоры, пайплайны, конфиги, векторные индексы и иногда целые куски Python-кода. Проблема не в том, что модель «хочет» атаковать систему. Модель ничего не хочет. Проблема в том, что разработчик скачал красивый файл из интернета, вызвал torch.load или pickle.load и дал загрузчику права своего окружения. Блестящий план, если цель - проверить, сколько секретов можно потерять за одну строку.
Как работает десериализация и почему ИИ тут ни при чем
Сериализация превращает объект программы в последовательность байтов, чтобы сохранить объект на диск или передать по сети. Десериализация делает обратное: читает байты и восстанавливает объект. JSON обычно хранит простые структуры: строки, числа, массивы и словари. CSV хранит таблицу. Такие форматы сами по себе не умеют запускать произвольный Python-код.
В машинном обучении долго царил другой подход. Разработчикам нужно было сохранять не только массивы чисел, но и состояние модели, оптимизатора, препроцессора, пользовательские классы и служебные объекты. Python дал удобный инструмент - pickle. Удобство оказалось с лезвием внутри: pickle может восстанавливать сложные объекты и вызывать функции при загрузке. Поэтому недоверенный pickle-файл надо считать не данными, а потенциально исполняемым артефактом.
Та же логика касается форматов, которые внутри используют pickle или похожие механизмы. Расширение .pt, .pth, .bin, .pkl или .joblib не доказывает безопасность. Файл может быть обычными весами, а может быть контейнером, который при загрузке дергает опасный путь в рантайме. Никакой мистики, никакого «восстания машин», просто старый класс уязвимостей CWE-502 в новой упаковке с неоновой наклейкой AI.
В документации PyTorch к torch.load прямо сказано: не загружайте данные из недоверенного источника. В новых версиях PyTorch загрузку сделали осторожнее: режим weights_only ограничивает десериализацию тензорами, примитивами, словарями и явно разрешенными типами. В PyTorch 2.6 этот режим стал поведением по умолчанию для многих сценариев. Но старые версии, старые чекпойнты и проекты с пользовательскими классами никуда не исчезли. Зоопарк жив, кормить его приходится руками.
Где атака появляется на практике
Самый обычный сценарий выглядит невинно. Исследователь находит модель на Hugging Face, GitHub, в архиве коллеги или в чате, скачивает файл и запускает ноутбук:
import torch
model = torch.load("model.pth")
Если файл собрал доверенный автор, риск ниже. Если происхождение модели мутное, строка выше превращает ноутбук в приемный пункт чужих инструкций. При загрузке вредоносный артефакт может попытаться выполнить код в окружении пользователя. Локально под ударом оказываются токены API, переменные окружения, SSH-ключи, конфиги облаков и исходники. На сервере риск тяжелее: MLOps-пайплайн часто видит внутренние хранилища, реестры контейнеров, бакеты, сервисные токены и инфраструктурные секреты.
Вред может идти не только через захват системы. В технике Sleepy Pickle, о которой писал SecurityLab, pickle-артефакт может изменить поведение модели при загрузке. Такой сценарий неприятнее грубого вируса: модель продолжает работать, но отвечает иначе, подмешивает вредные результаты, меняет выходные данные или ведет себя как зараженный компонент цепочки поставки. Пользователь видит «модель загрузилась», а не «кто-то полез в процесс Python с монтировкой».
Более безопасная схема в PyTorch выглядит иначе. Архитектуру модели описывают в коде, а из файла грузят только веса:
import torch
from my_model import Net
model = Net()
state_dict = torch.load("weights.pth", weights_only=True, map_location="cpu")
model.load_state_dict(state_dict)
model.eval()
Такой подход не делает файл священным, но режет главную опасность: загрузчик не должен восстанавливать произвольные Python-объекты. Если старый чекпойнт требует weights_only=False, это уже красный флаг. Не «ошибка совместимости», которую надо скорее заглушить, а повод спросить, почему модель хочет загрузиться как исполняемый объект.
Почему safetensors помогает, но не спасает всё человечество
Формат safetensors появился как более безопасный способ хранить веса моделей. Он хранит тензоры и метаданные, а не произвольные Python-объекты. Поэтому файл .safetensors не должен запускать код при обычной загрузке весов. Пример выглядит скучно, а безопасность часто как раз скучная:
from safetensors.torch import load_file
from my_model import Net
model = Net()
state_dict = load_file("model.safetensors")
model.load_state_dict(state_dict)
model.eval()
Но safetensors не надо превращать в амулет. Формат снижает риск выполнения кода через файл весов, но не проверяет честность автора, не ищет бэкдор в поведении модели, не анализирует requirements.txt, не запрещает вредный код в modeling.py и не спасает от включенного trust_remote_code=True. Если репозиторий говорит «вот безопасные веса, а теперь запусти наш скрипт установки», опасность могла просто переехать из модели в соседний файл.
Сканеры тоже нужны, но без религиозного восторга. ModelScan, Fickling, PickleScan и похожие инструменты могут найти подозрительные конструкции в ML-артефактах. Их стоит включать в проверку, особенно перед тем как модель попадет в общий пайплайн. Но сканер не доказывает, что файл безопасен. Он доказывает только, что конкретный инструмент с конкретными правилами не нашел известный ему набор проблем. Для индустрии, которая любит продавать «ИИ для всего», мысль неприятная, но полезная.
pip install modelscan
modelscan -p ./models
pip install fickling
fickling --check-safety suspicious_model.pkl
Материал предназначен для легального и ответственного использования. Проверяйте только свои модели, собственную инфраструктуру или системы, на тестирование которых у вас есть разрешение. Соблюдайте законы своей страны, особенно России. Не применяйте техники анализа моделей для несанкционированного доступа, слежки, взлома, нарушения правил сервисов или незаконного обхода ограничений.
Типичные ошибки команд
Первая ошибка - верить расширению. .pt не значит «безопасно», .pth не значит «только веса», .bin не значит «просто бинарник с числами». Перед загрузкой нужно понимать формат, источник и механизм чтения.
Вторая ошибка - грузить чужие модели в среде с секретами. Ноутбук разработчика часто хранит токены Hugging Face, OpenAI, AWS, GitHub, SSH-ключи и доступ к рабочим репозиториям. Загрузка непроверенного артефакта в такой среде похожа на экскурсию незнакомца по бухгалтерии с правом открывать все шкафы.
Третья ошибка - включать trust_remote_code=True без анализа. В экосистеме Transformers этот параметр нужен для моделей с кастомным кодом. Иногда без него модель не запустится. Но с точки зрения безопасности вы уже не просто грузите веса, а соглашаетесь выполнить код автора репозитория.
Четвертая ошибка - думать, что публичный хаб равен проверенному поставщику. Hugging Face и другие площадки используют проверки, предупреждения и сканирование, но хаб не превращает каждый репозиторий в доверенный релиз. Проверять автора, коммиты, хеши, обсуждения, лицензию и историю изменений всё равно придется живым людям. Увы, пока отдел закупок душ не принимает, остается инженерная гигиена.
Пятая ошибка - лечить риск одной настройкой. weights_only=True полезен. Safetensors полезен. Сканер полезен. Контейнер полезен. Но ни одна мера не закрывает всю цепочку поставки. Работает набор защит: безопасный формат, минимальные права, изоляция, проверка источника, контроль зависимостей, журнал загрузок и запрет на прямой путь «скачал из интернета - отправил в продакшен».
| Что загружают | Где риск | Как действовать |
|---|---|---|
.pkl, .joblib |
Pickle может выполнить код при десериализации | Не грузить в рабочей среде, проверять, запускать только в изоляции |
.pt, .pth, .bin |
Внутри может быть pickle или сложный чекпойнт | Использовать weights_only=True, хранить архитектуру отдельно |
.safetensors |
Меньше риск RCE через веса, но остается риск кода рядом | Проверять репозиторий, зависимости и параметры запуска |
Модель с trust_remote_code=True |
Запуск пользовательского кода автора модели | Читать код, фиксировать версии, запускать в песочнице |
| MLOps-пайплайн | Доступ к секретам, бакетам, реестрам и внутренним сервисам | Ограничивать права, изолировать загрузку, вести журнал артефактов |
Как безопаснее встроить модели в рабочий процесс
Сначала относитесь к модели как к поставляемому компоненту, а не как к картинке из интернета. У компонента должен быть источник, версия, хеш, дата загрузки, лицензия и владелец внутри команды. Если нельзя ответить, кто принес модель и почему ей доверяют, модель не должна ехать в продакшен.
Для экспериментов используйте одноразовые контейнеры без рабочих секретов и без доступа к внутренней сети. Для продакшена пересохраняйте веса в безопасный формат, если фреймворк и архитектура позволяют. Проверяйте зависимости отдельно. Не ставьте пакеты из requirements.txt вслепую, особенно если репозиторий маленький, свежий, без понятной истории и требует странные версии библиотек.
В CI/CD добавьте проверку ML-артефактов как отдельный этап. Минимальный набор: запрет на pickle без исключения, сканирование моделей, контроль хешей, запуск тестовой загрузки в контейнере, журнал источников и ручное подтверждение для артефактов с удаленным кодом. Да, звучит менее романтично, чем «мы внедрили ИИ». Зато потом не придется объяснять, почему «инновационная модель» унесла токены облака.
FAQ
Десериализация ИИ - это отдельная новая уязвимость?
Нет. Основа старая: небезопасная десериализация недоверенных данных. Новизна в контексте. ML-команды массово обмениваются моделями, чекпойнтами и пайплайнами, поэтому старый класс уязвимостей попал в цепочку поставки ИИ.
Почему pickle до сих пор используют, если формат опасен?
Pickle удобен: он сохраняет сложные Python-объекты почти без лишней работы. В исследовательской среде удобство долго побеждало безопасность. Проблема начинается, когда такой формат переносят в публичный обмен моделями и продакшен.
Можно ли просто всегда ставить weights_only=True?
Нужно ставить, когда сценарий позволяет. Но часть старых чекпойнтов и моделей с пользовательскими классами так не загрузится. Если файл требует
weights_only=False, его надо проверять строже и открывать только в изоляции.
Safetensors полностью решает проблему?
Нет. Safetensors снижает риск выполнения кода через файл весов, потому что хранит тензоры и метаданные. Но формат не проверяет вредный код в репозитории, зависимости, Docker-образ, параметры запуска и закладки в поведении модели.
Сканер моделей дает гарантию безопасности?
Нет. Сканер полезен как фильтр, но не как печать «безопасно». Его нужно сочетать с безопасными форматами, изоляцией, проверкой источника, ограниченными правами и контролем зависимостей.
Десериализация моделей ИИ опасна не потому, что нейросети стали коварными. Коварство тут человеческое и очень приземленное: взять неизвестный файл, дать ему загрузчик с правами разработчика и назвать процесс инновацией. Правильная позиция проще и суше: модель в небезопасном формате считается исполняемым артефактом, пока команда не доказала обратное. Проверяйте источник, режьте права, используйте безопасные форматы, не запускайте чужой код в рабочей среде и не путайте хайп вокруг ИИ с отменой базовой гигиены.