SAST + LLM = ?

  • #Уязвимости
  • #Linux

О чем материал

Рассказываем, как перестать выбирать и начать жить подружить SAST и LLM

Классический SAST уже не вытягивает современную сложность кодовых баз (будем честны: никогда не вытягивал). LLM'ки же все еще слишком забывчивы и склонны к галлюцинациям, чтобы вот так взять и всецело доверить им вопросы безопасности проектов. Но если дополнить формальный SAST гибкой ИИ-прослойкой, получается вполне рабочий тандем. SAST дает строгие правила и точную локализацию, ИИ — широкий охват, понимание бизнес-логики, фильтрацию шума и автофиксы. 

Давайте разбираться, что говорят об этом подходе академические исследования, при чем здесь графовые представления кода и эмбеддинги, кто уже продает SAST + LLM как продукт и к чему все идет в перспективе ближайших лет. 

Битва несовершенных

Начнем с банального, но честного наблюдения:

  • Формальный SAST (PT AI, SonarQube, CodeQL, Semgrep и др.) хорошо ловит типовые, формализуемые баги по четким правилам, но быстро упирается в новые фреймворки, кастомные библиотеки или хитрые бизнес-правила. И все — привет, false negative…
  • ML/LLM могут читать код как текст, понимать контекст и ловить нетривиальные шаблоны, но при этом страдают от галлюцинаций и нестабильности. Встретилось слово «eval» — и модель уже в панике, даже если туда никогда не попадет пользовательский ввод.

То есть SAST дает высокий precision и приличную локализацию, но низкий recall — много уязвимостей проходит мимо. У LLM/ML высокий recall (ловят все, что движется, а что не движется — двигают и ловят), но полно лишнего шума, не всегда понятные объяснения и «некоторые» сложности с контекстным окном на нетривиальных трассах выполнения.

Бенчмарки показывают, что при обнаружении уязвимостей LLM (уровня GPT-4.1, Mistral Large, DeepSeek V3) показывают F1 ≈ 0,75–0,80, а у классических SAST-инструментов (вроде SonarQube и CodeQL) F1 ≈ 0,26–0,55, но заметно меньше ложных срабатываний 

Так что же выбрать… Вместо того, чтобы спорить, лучше использовать и SAST, и LLM/ML, но в разных ролях. Пусть SAST остается железобетонным, формальным драйвером, а LLM — хоть и не очень надежными, зато умными и адаптивными помощниками? Или все-таки наоборот?

Классический SAST и его болезни

Формальные SAST-инструменты, какими бы сложными они ни казались, работают примерно по одному сценарию:

  • парсят код в AST либо в байт-код, преобразуют его в CFG, графы потоков данных и прочие модели приложения;
  • гоняют по всему этому заранее заданные экспертные правила, паттерны и спецификации источников/приемников (source/sink в терминах тейнт-анализа);
  • выдают детерминированный список находок, где каждая уязвимость привязана к конкретным местам в коде.

«Все красиво на бумаге…»: мало ложных срабатываний, понятные отчеты, легко встроить в CI/CD. Но в реальности выясняется несколько нюансов. Детали ищите здесь, а пока перечислю вкратце: 

  1. Ограниченность правилами. Если вы не описали, что requests.get().text — это пользовательский ввод, SAST посмотрит на эту конструкцию со слоновьим спокойствием. Информация о том, что речь идет о потенциально опасных параметрах HTTP-запроса, сама в правилах не напишется.
  2. False negatives. Любая новая библиотека, фреймворк (привет фронтендерам!) или нестандартный паттерн — и формальный анализ ничего не увидит, пока вы не напишете руками еще одну пачку правил. Исследования говорят, что это типичная и самая распространенная причина пропусков.
  3. Path explosion и false positives. В попытке упростить модель приложения (чтобы результаты анализа не пришлось разбирать нашим внукам из-за экспоненциального роста исследуемых путей выполнения) анализатор часто считает, что «по какому-то пути это все-таки может случиться». Поэтому вполне безопасные кейсы помечаются как потенциально уязвимые. Результат — большой объем шумных находок для последующего триажа.
  4. Отсутствие спецификации бизнес-логики и модели угроз. Одно дело — определить SQL-инъекцию, а совсем другое — заметить, что в логике авторизации можно перескочить через check. Здесь формальные паттерны быстро заканчиваются, и это неудивительно: анализатору неоткуда взять эталонные (еще и формальные) спецификации бизнес-логики или модели угроз, относительно которых необходимо проверять код.

ML и LLM: сильные и слабые стороны

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

Плюсы:

  • Высокий recall. На бенчмарках LLM (ChatGPT, Mistral Large, DeepSeek V3) уверенно обходят классический SAST по F1 и особенно полноте. Они видят значительно больше потенциальных уязвимостей, чем правилоориентированные анализаторы.
  • Контекст и бизнес-логика. LLM не ограничены синтаксисом. Если показать им спеки архитектуры, комментарии или документацию, модели смогут рассуждать, к примеру, о логических уязвимостях (проблемах доступа, нарушениях автоматных состояний, условиях гонок и т. п).
  • Некорректный код и неизвестные языки. LLM вообще без разницы, на каком языке написан анализируемый код, дописан ли он «до точки» и собирается ли без ошибок (привет авторам чудесных ГОСТов, застрявшим в 1980-х). С точки зрения модели код — это просто текст. Если его можно прочитать, значит, с ним можно работать.
  • Человекочитаемые отчеты. В отличие от SAST, который выдает «rule CWE-89 triggered at line 42» с типовыми рекомендациями, LLM способны выдавать: «Здесь возможна SQL-инъекция, потому что эта строка формируется конкатенацией пользовательского ввода без параметризации вот здесь, что можно исправить так».

Минусы (увы, тоже системные):

  • Галлюцинации. Модель может увидеть уязвимость там, где ее нет, или неправильно локализовать проблему. Показательный пример: eval от собираемой из частей константы, который корректный SAST легко определит как безопасный, заставит LLM тревожиться. Разбор подобных кейсов ищите здесь
  • Нечеткие гарантии. Нельзя формально доказать, что модель не «забудет» класс уязвимости или не начнет массово ошибаться на новых паттернах. Более того, воспроизводимость результатов от анализа к анализу на одной и той же кодовой базе еще нужно заслужить.
  • Чувствительность. Чуть изменили промпт, саму модель или ее параметры — получите другой результат. Для аппсеков это диагноз, а не фича :) 

Тем не менее именно из этой асимметрии и рождается идея гибридного решения: SAST обеспечивает строгость, а LLM — обобщающее понимание. А прийти к этому гибриду можно двумя способами...

Затыкаем недостатки SAST с помощью ИИ

Идея усилить существующий SAST-продукт ИИ-фичами очевидна. Это делает ее весьма привлекательной для вендоров, в портфеле которых есть классические SAST-решения…

ИИ вполне можно делегировать:

  • Триаж результатов, полученных формальными средствами. Телодвижения по интеграции потребуются минимальные, особенно в случае работы в CI/CD-пайплайне под чутким наздором ASOC. Отдельно можно рассмотреть вариант с триаж-копайлотом.
  • Генерацию эксплоитов, рекомендаций по устранению и патчей. При правильном подходе к формированию контекста это может дать ошеломляющие результаты. Удивительный, хотя и закономерный факт: LLM’ки устраняют уязвимости гораздо лучше, чем находят.
  • Генерацию экспертизы. Сформировать набор правил для нового фреймворка или под специфику конкретного проекта — вполне посильная задача для современных LLM. Сюда же можно отнести и копайлоты для написания правил.
  • Автоопределение сущностей экспертизы. В отличие от формальных методов, распознающих точки входа/выхода данных и критичные точки выполнения, LLM способны определять их, исходя из названия код-символов и окружающего контекста выполнения.
  • Поиск в коде фрагментов, схожих с уже известными уязвимостями. Векторные представления будто для этого и были созданы — тут даже LLM не особо нужны.
  • Семантическая разметка кода. «Вот здесь у нас представление, тут — управление доступом, а это бизнес-логика». Такая разметка поможет формальным методам оптимизировать процесс сканирования и получать более точные результаты.

Список можно было бы продолжать, если бы не одно но... Большинство перечисленных минусов вполне можно закрыть, если подойти к задаче с другой стороны — использовать в роли драйвера анализа ИИ, а не формальные методы.

Затыкаем недостатки ИИ с помощью SAST

Очевидно, в этом случае роль формальных методов сводится к тому, чтобы обеспечить ИИ как можно более точным контекстом, но при этом минимальным/необходимым/достаточным. Чтобы SAST и LLM начали разговаривать на одном языке, можно посмотреть в сторону структурированных моделей приложения. 

Здесь в игру вступают AST, CFG, PDG/DFG и CPG, построение которых — родная задача для формальных подходов (и они более-менее успешно с ней справляются):

  • AST (Abstract Syntax Tree) и CST (Concrete Syntax Tree) — базовое синтаксическое дерево, которое строится практически в любом анализаторе. CodeQL, к примеру, опирается на AST-узлы для запросов по коду.
  • CFG (Control Flow Graph) — граф управления потоком, описывающий возможные переходы между инструкциями. Нужен для понимания порядка выполнения.
  • DFG/PDG (Data/Program Dependence Graph) — графы зависимостей данных и контроля, показывающие, откуда берется значение переменной и что на что влияет.

Над всем этим находится CPG (Code Property Graph) — суперграф, объединяющий AST, CFG, DFG/PDG и другие представления в единую формальную структуру. В части ИИ он становится удобным объектом для графовых нейросетей (GNN): они умеют распространять информацию по ребрам и учитывать как локальные, так и глобальные зависимости. Исследования говорят, что графовые представления кода — естественная среда для таких моделей.

Дальше можно подумать о векторизации — преобразовании кода в родное для LLM числовое представление (эмбеддинг), которое сохраняет его синтаксическую структуру и семантическое поведение для последующей обработки. Ряд моделей при этом рассматривают код не как plain-text, а именно в виде того или иного графа. Как раз то, что нам нужно, чтобы связать воедино формальные и ИИ-подходы!

МодельИспользуемые репрезентацииТип
code2vec / code2seqAST pathsPath-Attention over AST
GraphCodeBERTDFGTransformer + GNN ideas
CuBERTCFG featuresTransformer
Devign / CodeGNNAST + CFG + DFGGNN
CodeT5+AST tokensTransformer

По сути, эмбеддинги кода — это строительные блоки для ИИ над программами: на них можно строить классификаторы уязвимостей, ретриверы для RAG, подсказчики фиксов и т. п.

RAG для кода: как SAST помогает LLM не оторваться от реальности

Когда кода много (репозитории на десятки тысяч файлов), LLM’ке нельзя просто скормить все — подавится. Едва ли в ближайшее время стоит ожидать столь драматического увеличения пределов контекстного окна, что его хватит для проглатывания «в одно жало» даже средних по объему проектов. Скромно промолчим и о стоимости анализа, использующего подобный подход…

Чтобы закрыть эту проблему, нужна инфраструктура по отбору релевантных задаче фрагментов кода. Здесь на сцену снова выходит статика, но уже в роли службы доставки информации для LLM.

Типичный RAG-пайплайн для кода:

  1. Парсинг и разбиение (chunking). Код парсят в AST/CST и режут на логические фрагменты: функции, классы, связанные блоки.
  2. Статический анализ для сохранения контекста. Чтобы не получить обрезанные по смыслу фрагменты, можно использовать формальный анализ графа. Это поможет определить, какие узлы должны быть рядом, и при необходимости «склеить» поддеревья обратно.
  3. Эмбеддинг. Каждый фрагмент кода прогоняется через модель (code2vec, GraphCodeBERT или другую кодовую LLM) для получения векторного представления.
  4. Индексирование в векторной БД. Вектор + исходный код + краткое описание на естественном языке складываются в векторное хранилище (pgvector, Qdrant, Weaviate и т. п).
  5. Инференс. Когда LLM нужно ответить на вопрос или проверить уязвимость, она сначала берет в текущий контекст ближайшие фрагменты кода из векторного индекса, а уже потом начинает рассуждать.

Что характерно: без статического анализа (AST/CPG, аккуратный chunking) LLM будет видеть либо слишком мало контекста, либо слишком много постороннего мусора. Основанный на статике RAG-слой делает гибрид почти обязательным: SAST отвечает за структуру и связность, LLM — за понимание.

Альтернативный подход: никуда без агентов

У любого более-менее реального приложения большой CPG. Нет, не так… Он БОЛЬШОЙ. К примеру, полный CPG, построенный с помощью Joern для линуксового ядра, потребует около 80 Gb только для хранения. А ведь его еще надо обработать...

Конечно, можно (и нужно) сложить это все в кластер Neo4j или другой аналогичной графовой базы, навернуть сверху GraphRAG, научить LLM делать туда запросы и свести задачу к предыдущей. Но что, если пойти другим путем?

Давайте вспомним, как работают с кодом копайлоты и кодинг-агенты. Помимо grep, они могут использовать в качестве неизменного тула:

  • Индексированную базу исходников. Тот самый RAG, но «быстрый», по текстовому представлению кода и построенный с помощью специализированной модели (jina-embeddings-v2-base-code или аналогичной).
  • Markdown-спецификации, предварительно сгенерированные LLM по коду и описывающие его стек, архитектуру, допущения, бизнес-логику и т. п.
  • Доступ к средствам навигации по коду. Как правило, с помощью эндпоинтов LSP (language service provider) установленного в IDE языка. А иногда — через сторонние MCP-серверы (например, Bifrost).

Индекс и спецификации дают возможность на глаз определять скоуп кодовой базы, подходящий для решения текущей задачи, а навигация по коду позволяет выстроить из этого необходимый/достаточный контекст.

Так зачем заморачиваться с построением больших и сложных графов, если можно дать LLM возможность самостоятельно ходить по коду плюс-минус в соответствии с их структурой? Конечно, о полноценных CFG/DFG/PDG речи не идет, но для детектирования большинства классов уязвимостей будет вполне достаточно возможностей LSP (find_usages, go_to_definition, find_implementations, get_call/type_hierarchy и т. п). А с потоками данных внутри функции современные модели уже сейчас справляются на отлично. Согласитесь, выглядит как не менее рабочий и вполне перспективный вариант (еще и оптимальный с точки зрения ресурсных/временных затрат).

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

Научные исследования

Теперь к экспериментам, где гибридизации обоих родов реализованы не на слайдах, а в коде.

IRIS: LLM, которая пишет правила для SAST

Это классическая нейросимвольная система, которая использует LLM, чтобы генерировать спецификации источников/приемников для CodeQL (откуда берутся и куда «вытекают» данные), а также фильтровать ложные срабатывания, используя хитро построенный контекст.

На реальном репозитории IRIS нашел 55 подтвержденных уязвимостей против 27 у «чистого» CodeQL, включая 4 новые баги, ранее не описанные. При этом доля ложных сработок у LLM оказалась почти на 5 процентных пунктов ниже, чем у голого CodeQL.

SAST-Genius: LLM как интеллектуальный триаж Semgrep

SAST-Genius — это гибрид Semgrep + тонко настроенная LLM. Архитектура проста и линейна: Semgrep гонит свои правила и выдает длинный список findings, а LLM анализирует каждую находку с учетом ее контекста и дает пояснения/рекомендации. В результате количество ложных срабатываний падает примерно на 91% (с 225 до 20), а точность подскакивает до ~89,5% против 35,7% у «голого» Semgrep и 65,5% у GPT-4, работающего без SAST-подложки.

MoCQ: LLM, которая генерирует запросы для SAST

MoCQ автоматически генерирует и уточняет запросы к статическим анализаторам (например, CodeQL) вместо того, чтобы ждать экспертов... В результате LLM’ка достигает точности, сопоставимой с ручными экспертными правилами. Проще говоря, это уже не «LLM вместо SAST», а «LLM как усилитель SAST-правил».

LSAST и ZeroPath: LLM + поиск уязвимостей и бизнес-логики

В работе LSAST эксперты предлагают использовать локальную LLM вместе с системой поиска уязвимостей, чтобы лучше обнаруживать скрытые проблемы и заодно решить вопрос приватности (код не уходит в облако). ZeroPath, в свою очередь, демонстрирует в проде, как AI-native SAST с ML/LLM строит модель приложения, учитывающую потоки данных плюс бизнес-логику, и концентрируется на реальных уязвимостях. Есть кейсы, где «тысячи критических находок» после ИИ-триажа превращаются в несколько десятков реальных проблем.

Индустрия: как продают и внедряют гибридные решения 

На уровне продуктов картина следующая: одни делают «AI-native SAST» (полностью основанный на LLM/ML), другие — «AI-assisted» (классический анализатор + ИИ-слой triage/фиксов), а третьи — «AI-driven» (как правило, мультиагентные системы, использующие элементы SAST в качестве инструментов). Приведу несколько примеров.

AI-native:

AI-assisted:

  • Corgea.
  • Qwiet.ai.
  • Производители классических SAST-продуктов (Checkmarx, GitHub, Snyk, Veracode, Mend и др.) тоже активно обвешиваются ML/LLM-модулями.

AI-driven:

Также на рынке выделяются Guardrails, DeepSource, Raxis и другие производители, понемногу добавляющие ИИ-модули поверх классической статики. Тренд очевиден: SAST, который не умеет работать с ИИ, к 2026 г. будет выглядеть как SVN в мире Git.

Тренды: куда все это катится?

Если заглянуть чуть вперед, картина складывается следующая:

  1. AI-native vs AI-assisted/-driven. Полностью LLM’ные движки будут конкурировать с гибридами, но в корпоративном мире у последних явное преимущество: по ним проще давать гарантии и объяснения.
  2. Мультимодальные и мультиагентные анализаторы. Подход Endor Labs, где несколько ИИ-агентов смотрят на код с разных сторон, станет нормой. Один отвечает за синтаксис, другой — за потоки данных, третий — за бизнес-логику, четвертый — за зависимости и конфиги и т. п.
  3. Анализ на уровне IDE/PR. Инструменты вроде Arnica и Guardrails уже предлагают возможности анализа прямо при наборе кода и в PR, а также накладывают политики безопасности на лету. При массовом использовании генеративного кода другого выхода, кроме раннего и непрерывного контроля, просто нет: принцип shift-left никто не отменял.
  4. Фокус на новых классах уязвимостей и снижении шума. Традиционный SAST страдал от 50–95% ложных срабатываний и пропускал логические ошибки. Теперь приоритеты изменились: минимизировать шум, научиться ловить бизнес-логические дыры, мобильные и фронтенд-специфические уязвимости, а также уязвимости самих ИИ-компонентов (prompt injection, утечки токенов и т. п.).
  5. Стандартизация и бенчмарки. Появление открытых датасетов и методик сравнения означает, что при выборе SAST-решений рынок будет все больше опираться на публичные метрики, а не на маркетинг.
  6. Приватность и локальные модели. Опыт LSAST показывает, что многие опасаются отправлять код в сторонние LLM, поэтому локальные модели и гибриды с «hostable» или «SMOL-like» LLM’ками будут востребованы.

Выводы

Итак, резюмируем:

  • Формальный SAST остается обязательным игроком. Он обеспечивает детерминированность, объяснимость и не дает LLM впадать в маразм на больших объемах кода.
  • LLM дают ширину и глубину понимания. Они расширяют покрытие, видят новые паттерны, помогают работать с бизнес-логикой и человеческим контекстом, снижают шум и облегчают триаж.
  • Академические работы показывают, что вместе SAST и LLM показывают лучший результат как по полноте, так и по качеству срабатываний.
  • Гибридные продукты подтверждают, что это вполне практичный вариант. Уменьшение ложных срабатываний на 80–95%, новые классы обнаруживаемых уязвимостей, автофиксы и интеграция всего этого в привычный DevSecOps — уже реальность.

Будущее SAST — это не смерть формальных подходов под натиском ИИ, а эволюция в сторону нейросимвольных гибридов, где строгие правила и графы кода живут бок о бок с эмбеддингами, RAG и генеративными моделями.

Мы дěлаем Positive Research → для ИБ-экспертов, бизнеса и всех, кто интересуется ✽ {кибербезопасностью}