О чем материал
Рассказываем, как перестать выбирать и начать жить подружить 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. Но в реальности выясняется несколько нюансов. Детали ищите здесь, а пока перечислю вкратце:
- Ограниченность правилами. Если вы не описали, что requests.get().text — это пользовательский ввод, SAST посмотрит на эту конструкцию со слоновьим спокойствием. Информация о том, что речь идет о потенциально опасных параметрах HTTP-запроса, сама в правилах не напишется.
- False negatives. Любая новая библиотека, фреймворк (привет фронтендерам!) или нестандартный паттерн — и формальный анализ ничего не увидит, пока вы не напишете руками еще одну пачку правил. Исследования говорят, что это типичная и самая распространенная причина пропусков.
- Path explosion и false positives. В попытке упростить модель приложения (чтобы результаты анализа не пришлось разбирать нашим внукам из-за экспоненциального роста исследуемых путей выполнения) анализатор часто считает, что «по какому-то пути это все-таки может случиться». Поэтому вполне безопасные кейсы помечаются как потенциально уязвимые. Результат — большой объем шумных находок для последующего триажа.
- Отсутствие спецификации бизнес-логики и модели угроз. Одно дело — определить 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 / code2seq | AST paths | Path-Attention over AST |
| GraphCodeBERT | DFG | Transformer + GNN ideas |
| CuBERT | CFG features | Transformer |
| Devign / CodeGNN | AST + CFG + DFG | GNN |
| CodeT5+ | AST tokens | Transformer |
По сути, эмбеддинги кода — это строительные блоки для ИИ над программами: на них можно строить классификаторы уязвимостей, ретриверы для RAG, подсказчики фиксов и т. п.
RAG для кода: как SAST помогает LLM не оторваться от реальности
Когда кода много (репозитории на десятки тысяч файлов), LLM’ке нельзя просто скормить все — подавится. Едва ли в ближайшее время стоит ожидать столь драматического увеличения пределов контекстного окна, что его хватит для проглатывания «в одно жало» даже средних по объему проектов. Скромно промолчим и о стоимости анализа, использующего подобный подход…
Чтобы закрыть эту проблему, нужна инфраструктура по отбору релевантных задаче фрагментов кода. Здесь на сцену снова выходит статика, но уже в роли службы доставки информации для LLM.
Типичный RAG-пайплайн для кода:
- Парсинг и разбиение (chunking). Код парсят в AST/CST и режут на логические фрагменты: функции, классы, связанные блоки.
- Статический анализ для сохранения контекста. Чтобы не получить обрезанные по смыслу фрагменты, можно использовать формальный анализ графа. Это поможет определить, какие узлы должны быть рядом, и при необходимости «склеить» поддеревья обратно.
- Эмбеддинг. Каждый фрагмент кода прогоняется через модель (code2vec, GraphCodeBERT или другую кодовую LLM) для получения векторного представления.
- Индексирование в векторной БД. Вектор + исходный код + краткое описание на естественном языке складываются в векторное хранилище (pgvector, Qdrant, Weaviate и т. п).
- Инференс. Когда 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:
- ZeroPath.
- Endor Labs.
- Arnica (Arnie AI).
AI-assisted:
- Corgea.
- Qwiet.ai.
- Производители классических SAST-продуктов (Checkmarx, GitHub, Snyk, Veracode, Mend и др.) тоже активно обвешиваются ML/LLM-модулями.
AI-driven:
Также на рынке выделяются Guardrails, DeepSource, Raxis и другие производители, понемногу добавляющие ИИ-модули поверх классической статики. Тренд очевиден: SAST, который не умеет работать с ИИ, к 2026 г. будет выглядеть как SVN в мире Git.
Тренды: куда все это катится?
Если заглянуть чуть вперед, картина складывается следующая:
- AI-native vs AI-assisted/-driven. Полностью LLM’ные движки будут конкурировать с гибридами, но в корпоративном мире у последних явное преимущество: по ним проще давать гарантии и объяснения.
- Мультимодальные и мультиагентные анализаторы. Подход Endor Labs, где несколько ИИ-агентов смотрят на код с разных сторон, станет нормой. Один отвечает за синтаксис, другой — за потоки данных, третий — за бизнес-логику, четвертый — за зависимости и конфиги и т. п.
- Анализ на уровне IDE/PR. Инструменты вроде Arnica и Guardrails уже предлагают возможности анализа прямо при наборе кода и в PR, а также накладывают политики безопасности на лету. При массовом использовании генеративного кода другого выхода, кроме раннего и непрерывного контроля, просто нет: принцип shift-left никто не отменял.
- Фокус на новых классах уязвимостей и снижении шума. Традиционный SAST страдал от 50–95% ложных срабатываний и пропускал логические ошибки. Теперь приоритеты изменились: минимизировать шум, научиться ловить бизнес-логические дыры, мобильные и фронтенд-специфические уязвимости, а также уязвимости самих ИИ-компонентов (prompt injection, утечки токенов и т. п.).
- Стандартизация и бенчмарки. Появление открытых датасетов и методик сравнения означает, что при выборе SAST-решений рынок будет все больше опираться на публичные метрики, а не на маркетинг.
- Приватность и локальные модели. Опыт LSAST показывает, что многие опасаются отправлять код в сторонние LLM, поэтому локальные модели и гибриды с «hostable» или «SMOL-like» LLM’ками будут востребованы.
Выводы
Итак, резюмируем:
- Формальный SAST остается обязательным игроком. Он обеспечивает детерминированность, объяснимость и не дает LLM впадать в маразм на больших объемах кода.
- LLM дают ширину и глубину понимания. Они расширяют покрытие, видят новые паттерны, помогают работать с бизнес-логикой и человеческим контекстом, снижают шум и облегчают триаж.
- Академические работы показывают, что вместе SAST и LLM показывают лучший результат как по полноте, так и по качеству срабатываний.
- Гибридные продукты подтверждают, что это вполне практичный вариант. Уменьшение ложных срабатываний на 80–95%, новые классы обнаруживаемых уязвимостей, автофиксы и интеграция всего этого в привычный DevSecOps — уже реальность.
Будущее SAST — это не смерть формальных подходов под натиском ИИ, а эволюция в сторону нейросимвольных гибридов, где строгие правила и графы кода живут бок о бок с эмбеддингами, RAG и генеративными моделями.



