Sift or Get Off the PoC: Applying Information Retrieval to Vulnerability Research with SiftRank
Ресерч, Реализация
Одна из проблемных задач в исследовании уязвимостей связана с ограниченными ресурсами и расстановкой приоритетов.
Когда вы пытаетесь найти 0 day уязвимость в каком-либо продукте, вы сосредоточиваетесь в основном на каких-то интересных моментах кодовой базы и попросту мало времени и энергии, чтобы пытаться анализировать каждую строку.
Данную проблему попытались интересно решить через создание алгоритма ранжирования SiftRank.
Что считается “документом”
В контексте алгоритма документ — это элемент корпуса C, который мы хотим ранжировать относительно запроса (query).
В зависимости от задачи это может быть:
• функция (в patch diffing),
• call chain,
• фрагмент декомпилированного кода,
• иной структурированный объект.
В кейсе с SonicWall документами выступали call chains, полученные из diff изменённых функций.
Алгоритм состоит из
1) Батчинга документов по N значений, чтобы избежать превышения контекста LLM. Каждую итерацию документы случайно перемешивается и разбивается на батчи фиксированного размера S.
2) LLM ranking внутри батча.
LLM получает батч в виде словаря {id: документ} и возвращает упорядоченный список id(#1, #2 и т.д.) по релевантности к query.
Важно: модель не возвращает абсолютный score в диапазоне [0,1], а возвращает порядок.
3) Score calculation.
Позиция документа в батче преобразуется в числовой ранг.
Далее для каждого документа агрегируется статистика по нескольким итерациям (mean или median ранга).
Таким образом получается относительный score, отражающий среднюю позицию документа среди случайных выборок.
4) Этапы 1–3 проводятся в несколько итераций , что снижает зависимость от конкретной компоновки батча и нормализует распределение.
5) Определение точки перегиба τ.
После сортировки документов по агрегированному score строится распределение.
Точка τ определяется как “elbow” (inflection point) — место максимального изменения кривизны / резкого увеличения разрыва между соседними значениями.
Интуитивно — это граница между плотной группой релевантных элементов и длинным хвостом нерелевантных.
6) Iterative refinement.
После определения τ отбрасываются элементы ниже порога.
заново повторяем этапы 1–5 на оставшийся выборке .
Важно: на этапе refinement ранги пересчитываются заново только на сокращённом множестве, а не переиспользуются старые значения.
Размер корпуса уменьшается, количество батчей сокращается, и процесс повторяется до стабилизации τ или достижения лимита итераций.
Где применяли
Использовали в patch diffing на примере бинарника SonicWall с помощью BinDiff.
Сделали биндифф патча бинарника → получили 2 197 изменённых функций
Расширили до 2 713 call chains
Их SiftRank отфильтровал до 254 “подозрительных” цепочек
Построили call graph clustering
Получили 119 кластеров
Отсортировали кластеры по “mass × density”
Проверили руками и получили реальный один уязвимый cluster из 119
Из интересных трюков
• подают батчи как словарь, а от LLM требуют, чтобы вернула только список ключей, чтобы сократить output-токены
• можно просить LLM краткое объяснение по батчу, что помогает корректировать query и стабилизировать reasoning
Не сказать, что это что-то революционное для анализа уязвимостей или реверс-инжиниринга, но это интересный подход, который можно применить где-либо ещё с точки зрения ограниченных ресурсов и времени на выявление релевантных значений.
Вопросы остаются только к обобщаемости результатов, так как эксперимент проводился на одном бинарнике.
Ресерч, Реализация
Одна из проблемных задач в исследовании уязвимостей связана с ограниченными ресурсами и расстановкой приоритетов.
Когда вы пытаетесь найти 0 day уязвимость в каком-либо продукте, вы сосредоточиваетесь в основном на каких-то интересных моментах кодовой базы и попросту мало времени и энергии, чтобы пытаться анализировать каждую строку.
Данную проблему попытались интересно решить через создание алгоритма ранжирования SiftRank.
Что считается “документом”
В контексте алгоритма документ — это элемент корпуса C, который мы хотим ранжировать относительно запроса (query).
В зависимости от задачи это может быть:
• функция (в patch diffing),
• call chain,
• фрагмент декомпилированного кода,
• иной структурированный объект.
В кейсе с SonicWall документами выступали call chains, полученные из diff изменённых функций.
Алгоритм состоит из
1) Батчинга документов по N значений, чтобы избежать превышения контекста LLM. Каждую итерацию документы случайно перемешивается и разбивается на батчи фиксированного размера S.
2) LLM ranking внутри батча.
LLM получает батч в виде словаря {id: документ} и возвращает упорядоченный список id(#1, #2 и т.д.) по релевантности к query.
Важно: модель не возвращает абсолютный score в диапазоне [0,1], а возвращает порядок.
3) Score calculation.
Позиция документа в батче преобразуется в числовой ранг.
Далее для каждого документа агрегируется статистика по нескольким итерациям (mean или median ранга).
Таким образом получается относительный score, отражающий среднюю позицию документа среди случайных выборок.
4) Этапы 1–3 проводятся в несколько итераций , что снижает зависимость от конкретной компоновки батча и нормализует распределение.
5) Определение точки перегиба τ.
После сортировки документов по агрегированному score строится распределение.
Точка τ определяется как “elbow” (inflection point) — место максимального изменения кривизны / резкого увеличения разрыва между соседними значениями.
Интуитивно — это граница между плотной группой релевантных элементов и длинным хвостом нерелевантных.
6) Iterative refinement.
После определения τ отбрасываются элементы ниже порога.
заново повторяем этапы 1–5 на оставшийся выборке .
Важно: на этапе refinement ранги пересчитываются заново только на сокращённом множестве, а не переиспользуются старые значения.
Размер корпуса уменьшается, количество батчей сокращается, и процесс повторяется до стабилизации τ или достижения лимита итераций.
Где применяли
Использовали в patch diffing на примере бинарника SonicWall с помощью BinDiff.
Сделали биндифф патча бинарника → получили 2 197 изменённых функций
Расширили до 2 713 call chains
Их SiftRank отфильтровал до 254 “подозрительных” цепочек
Построили call graph clustering
Получили 119 кластеров
Отсортировали кластеры по “mass × density”
Проверили руками и получили реальный один уязвимый cluster из 119
Из интересных трюков
• подают батчи как словарь, а от LLM требуют, чтобы вернула только список ключей, чтобы сократить output-токены
• можно просить LLM краткое объяснение по батчу, что помогает корректировать query и стабилизировать reasoning
Не сказать, что это что-то революционное для анализа уязвимостей или реверс-инжиниринга, но это интересный подход, который можно применить где-либо ещё с точки зрения ограниченных ресурсов и времени на выявление релевантных значений.
Вопросы остаются только к обобщаемости результатов, так как эксперимент проводился на одном бинарнике.
🍓3👍1👀1
Вы даете задачи LLM неправильно!
Что если я скажу, что вы, скорее всего, неправильно работаете или даже кодите с LLM. Если у вас есть опыт уже боевого кодинга, скорее всего, вы планируете реализацию фичей за фичей, и это логично, ведь помещением всех целей в LLM, которые вы хотите реализовать, в контекст сразу, скорее всего, снизит качество этих фич, ведь LLM будет "разбрасываться" по задачам.
Ну и логично строить свою разработку фича за фичей, ведь можно удобно откатить фичу, если вдруг что-то сломается.
Так что есть исследование LLMs Get Lost In Multi-Turn Conversation, которое показывает, что, подавая изначально всю необходимую информацию в LLM, вы получаете намного большую точность, нежели подавая по чуть-чуть — incremental feeding.
В защиту вы, наверное, предположили: а что если в конце доносить всю информацию вместе или с каждым шагом повторять предыдущий контекст? Так вот, эксперимент учел такие "костыли".
В эксперименте было 5 видов подачи задачи:
FULL — полное ТЗ, описание как есть без какой-то модификации.
CONCAT — состоящая из разделенных "шардов", но по сути она собрана в один промпт.
SHARDED — шардированная, в LLM шаг за шагом отправляли разбитую задачу по этапам.
RECAP — точно такой же, как SHARDED, но в конце подавались все шарды вместе.
SNOWBALL — все вытекает из названия, инкрементальный способ, где на 1-м ходу подавался 1 шард, на втором — 1 + 2, на третьем — 1 + 2 + 3 и т.д.
Проводили на разных задачах, состоящих из кодинга, математики, работы с БД, суммаризации.
Точность FULL составила 90%, и CONCAT — 85.5–87.0%.
Точность же подхода шаг за шагом более удручающая — в районе 60–70%.
Почему так происходит?
Отдельные исследования показывают, что при уменьшении доступного контекста и росте глубины диалога качество постепенно деградирует. Например тык. Но здесь важен другой эффект.
Transformer не «переписывает» ранние hidden states задним числом.
Когда модель начинает решать задачу на основе неполной информации, она формирует промежуточную гипотезу. Поздние уточнения не модифицируют уже сгенерированные токены и не пересобирают внутренние состояния прошлых шагов — они лишь учитываются при дальнейшем дополнении.
То есть проблема не в том, что модель «разбрасывается», а в том, что ранняя частичная постановка задачи может сформировать устойчивую траекторию рассуждений.
Поэтому помните, что ИИ не программист, и не стоит отдавать ему таск за таском, формулируйте конечную просьбу и смело отдавайте.
Что если я скажу, что вы, скорее всего, неправильно работаете или даже кодите с LLM. Если у вас есть опыт уже боевого кодинга, скорее всего, вы планируете реализацию фичей за фичей, и это логично, ведь помещением всех целей в LLM, которые вы хотите реализовать, в контекст сразу, скорее всего, снизит качество этих фич, ведь LLM будет "разбрасываться" по задачам.
Ну и логично строить свою разработку фича за фичей, ведь можно удобно откатить фичу, если вдруг что-то сломается.
Так что есть исследование LLMs Get Lost In Multi-Turn Conversation, которое показывает, что, подавая изначально всю необходимую информацию в LLM, вы получаете намного большую точность, нежели подавая по чуть-чуть — incremental feeding.
В защиту вы, наверное, предположили: а что если в конце доносить всю информацию вместе или с каждым шагом повторять предыдущий контекст? Так вот, эксперимент учел такие "костыли".
В эксперименте было 5 видов подачи задачи:
FULL — полное ТЗ, описание как есть без какой-то модификации.
CONCAT — состоящая из разделенных "шардов", но по сути она собрана в один промпт.
SHARDED — шардированная, в LLM шаг за шагом отправляли разбитую задачу по этапам.
RECAP — точно такой же, как SHARDED, но в конце подавались все шарды вместе.
SNOWBALL — все вытекает из названия, инкрементальный способ, где на 1-м ходу подавался 1 шард, на втором — 1 + 2, на третьем — 1 + 2 + 3 и т.д.
Проводили на разных задачах, состоящих из кодинга, математики, работы с БД, суммаризации.
Точность FULL составила 90%, и CONCAT — 85.5–87.0%.
Точность же подхода шаг за шагом более удручающая — в районе 60–70%.
Почему так происходит?
Отдельные исследования показывают, что при уменьшении доступного контекста и росте глубины диалога качество постепенно деградирует. Например тык. Но здесь важен другой эффект.
Transformer не «переписывает» ранние hidden states задним числом.
Когда модель начинает решать задачу на основе неполной информации, она формирует промежуточную гипотезу. Поздние уточнения не модифицируют уже сгенерированные токены и не пересобирают внутренние состояния прошлых шагов — они лишь учитываются при дальнейшем дополнении.
То есть проблема не в том, что модель «разбрасывается», а в том, что ранняя частичная постановка задачи может сформировать устойчивую траекторию рассуждений.
Поэтому помните, что ИИ не программист, и не стоит отдавать ему таск за таском, формулируйте конечную просьбу и смело отдавайте.
🔥4👍2🤯1
Месяц назад был представлен отчет Latio по анализу AppSec-решений рынка на 2026 год.
В котором есть напрашивающиеся выводы, но я бы хотел остановиться на одном.
Безопасность цепочки поставок (supply chain security) расширяется и выходит за рамки CVE: теперь она включает обнаружение вредоносного кода, оценку «здоровья» пакетов (package health) и практики безопасного использования зависимостей по умолчанию (secure-by-default consumption). Одного лишь обнаружения CVE уже недостаточно для современной безопасности цепочки поставок.
Сейчас проблему безопасности цепочки поставок частично решают инструменты анализа зависимостей (SCA). Но в докладе между строк мелькает мысль, что SCA требуют переосмысления.
▸ В чем проблема SCA?
Основная проблема SCA всегда заключалась в том, что обновление open source-зависимостей — это сложно. Множество зависимостей просто нельзя обновить ввиду того, что ломается окружение, даже при условии, что это микросервисная архитектура (я в своем опыте встречал кейсы, когда обновление несвязанного с другим микросервиса ломало работу второго).
В целом SCA — это очень топорный подход к выявлению уязвимых зависимостей: SCA смотрит, что зависимость установлена, однако она может:
• Не использоваться;
• Функции и уязвимые методы не применяются;
• Использоваться, но не иметь контекста, при котором CVE будет применима, и так далее.
▸ Что же делать?
Внедрение анализа breaking changes — один из самых перспективных методов, помогающих процессу обновления. Breaking change = изменение в библиотеке, которое ломает код. Подход использует анализ достижимости на уровне функций (function-level reachability), чтобы определить изменения функций между версиями и предложить, какие именно изменения нужно внести для успешного патчинга. LLM усиливают такой анализ тем, что читают diff, changelog патча, а также контекст использования зависимости для того, чтобы сказать, стоит ли обновляться или нет.
В SCA акцент смещается с поиска уязвимостей на поиск малвари. Если раньше SCA был про то, как найти все уязвимые пакеты, то сейчас это движется в сторону того, чтобы оценить риск зависимости до того, как она станет проблемой. Сейчас тот же подход AI-driven SAST может частично историю с тем, чтобы учитывать контекст выполнения небезопасных зависимостей и понимать, вызываются ли опасные функции.
История с SCA лишь подчеркивает что в условиях вездесущего использования AI в AppSec-продуктах рынок движется к более комплексному подходу, где важен не сам факт наличия уязвимости, а ее реальный риск и контекст использования.
В котором есть напрашивающиеся выводы, но я бы хотел остановиться на одном.
Безопасность цепочки поставок (supply chain security) расширяется и выходит за рамки CVE: теперь она включает обнаружение вредоносного кода, оценку «здоровья» пакетов (package health) и практики безопасного использования зависимостей по умолчанию (secure-by-default consumption). Одного лишь обнаружения CVE уже недостаточно для современной безопасности цепочки поставок.
Сейчас проблему безопасности цепочки поставок частично решают инструменты анализа зависимостей (SCA). Но в докладе между строк мелькает мысль, что SCA требуют переосмысления.
▸ В чем проблема SCA?
Основная проблема SCA всегда заключалась в том, что обновление open source-зависимостей — это сложно. Множество зависимостей просто нельзя обновить ввиду того, что ломается окружение, даже при условии, что это микросервисная архитектура (я в своем опыте встречал кейсы, когда обновление несвязанного с другим микросервиса ломало работу второго).
В целом SCA — это очень топорный подход к выявлению уязвимых зависимостей: SCA смотрит, что зависимость установлена, однако она может:
• Не использоваться;
• Функции и уязвимые методы не применяются;
• Использоваться, но не иметь контекста, при котором CVE будет применима, и так далее.
▸ Что же делать?
Внедрение анализа breaking changes — один из самых перспективных методов, помогающих процессу обновления. Breaking change = изменение в библиотеке, которое ломает код. Подход использует анализ достижимости на уровне функций (function-level reachability), чтобы определить изменения функций между версиями и предложить, какие именно изменения нужно внести для успешного патчинга. LLM усиливают такой анализ тем, что читают diff, changelog патча, а также контекст использования зависимости для того, чтобы сказать, стоит ли обновляться или нет.
В SCA акцент смещается с поиска уязвимостей на поиск малвари. Если раньше SCA был про то, как найти все уязвимые пакеты, то сейчас это движется в сторону того, чтобы оценить риск зависимости до того, как она станет проблемой. Сейчас тот же подход AI-driven SAST может частично историю с тем, чтобы учитывать контекст выполнения небезопасных зависимостей и понимать, вызываются ли опасные функции.
История с SCA лишь подчеркивает что в условиях вездесущего использования AI в AppSec-продуктах рынок движется к более комплексному подходу, где важен не сам факт наличия уязвимости, а ее реальный риск и контекст использования.
🌭4👍2
Loop Engineering: Часть 1. Automatic Prompt Optimization
Наверное многие видели твит Бориса Черного, разработчика Claude Code, который заявил что больше не пишет код, а его задача теперь только создавать циклы (loop) для автономных агентов.
Соглашусь с этим мнением, сейчас когда агенты стали более автономными, а LLM более адаптированными под работу с агентами, улучшать агентов теперь могут сами же эти агенты путем анализа собственных трейсов. Но начать хотелось бы с простого, а именно с оптимизации промптов.
Промпты для агентов могут оптимизироваться автоматически. Этот подход называется Automatic Prompt Optimization, и для него были созданы специальные фреймворки, такие как DSPy, MIPROv2, GEPA и другие.
В эпоху текущих frontier моделей, оптимизация промтов не особо улучшает, модели очень точно отвечает и пишет код даже на плохо заданный промпт. Но если вы работаете с open source моделями, APO дает явный прирост по результатам.
В основе большинства таких систем лежит цикл из нескольких шагов:
1. Генерация новых вариантов инструкций.
2. Оценка качества на наборе примеров.
3. Выбор лучших кандидатов.
4. Повторение процесса.
Вместо ручного подбора промптов этот loop выполняется автоматически.
Все они работают по-своему. Какие-то оптимизируют только инструкции, какие-то дополнительно оптимизируют поведение агентов, структуру execution loop или даже весь harness. Сегодня же мы посмотрим только на оптимизацию промптов и сравним два подхода: PromptWizard и MIPROv2.
MIPROv2
MIPROv2 (Multi-prompt Instruction Proposal Optimizer v2) — один из оптимизаторов в DSPy.
Если сильно упростить, его задача состоит в том, чтобы найти комбинацию инструкции и few-shot примеров, которая дает лучший результат на вашей задаче.
Для этого MIPROv2 сначала анализирует train-датасет и пытается понять какие инструкции и примеры вообще могут помочь модели решать задачу лучше. После этого через LLM генерируется большое количество кандидатов инструкций и наборов few-shot примеров.
Дальше начинается самое интересное.
Полный перебор всех комбинаций инструкций и примеров был бы слишком дорогим, поэтому MIPROv2 использует байесовскую оптимизацию. На каждой итерации фреймворк выбирает наиболее перспективных кандидатов, запускает их на train/validation выборке, измеряет качество по заданной метрике и использует полученные результаты для выбора следующего набора кандидатов.
По сути MIPROv2 пытается ответить на вопрос:
Если у меня есть ограниченный бюджет на эксперименты, какие варианты промптов стоит проверить следующими, чтобы быстрее найти лучший результат?
В результате вместо ручного перебора десятков инструкций этот цикл выполняется автоматически.
PromptWizard
PromptWizard — фреймворк от Microsoft, который использует совершенно другой подход.
Если MIPROv2 больше похож на поиск лучшей инструкции среди большого количества кандидатов, то PromptWizard делает ставку на последовательное улучшение инструкции через feedback loop.
Вместо того чтобы генерировать сотни вариантов и выбирать лучший, PromptWizard пытается понять почему текущий промпт работает плохо и как его можно улучшить.
В его основе лежат три ключевые идеи:
1. Итеративная оптимизация промпта через feedback loop.
2. Оптимизация инструкции и few-shot примеров одновременно.
3. Автоматическое построение reasoning traces для примеров.
Упрощенно цикл выглядит следующим образом:
1. Генерируется инструкция.
2. Инструкция запускается на датасете.
3. LLM анализирует ошибки.
4. LLM предлагает улучшения.
5. Создается новая версия инструкции.
6. Цикл повторяется.
Фактически модель выступает одновременно в роли исполнителя, критика и автора следующей версии промпта.
Из-за этого PromptWizard больше похож не на search-задачу, как MIPROv2, а на self-refinement цикл, где каждая новая версия инструкции строится на основе ошибок предыдущей.
В следующей части посмотрим как агенты могут оптимизровать/переписывать других агентов или самих себя
Наверное многие видели твит Бориса Черного, разработчика Claude Code, который заявил что больше не пишет код, а его задача теперь только создавать циклы (loop) для автономных агентов.
“И такой переход мы будем наблюдать в течение всего оставшегося года”
Соглашусь с этим мнением, сейчас когда агенты стали более автономными, а LLM более адаптированными под работу с агентами, улучшать агентов теперь могут сами же эти агенты путем анализа собственных трейсов. Но начать хотелось бы с простого, а именно с оптимизации промптов.
Промпты для агентов могут оптимизироваться автоматически. Этот подход называется Automatic Prompt Optimization, и для него были созданы специальные фреймворки, такие как DSPy, MIPROv2, GEPA и другие.
В эпоху текущих frontier моделей, оптимизация промтов не особо улучшает, модели очень точно отвечает и пишет код даже на плохо заданный промпт. Но если вы работаете с open source моделями, APO дает явный прирост по результатам.
В основе большинства таких систем лежит цикл из нескольких шагов:
1. Генерация новых вариантов инструкций.
2. Оценка качества на наборе примеров.
3. Выбор лучших кандидатов.
4. Повторение процесса.
Вместо ручного подбора промптов этот loop выполняется автоматически.
Все они работают по-своему. Какие-то оптимизируют только инструкции, какие-то дополнительно оптимизируют поведение агентов, структуру execution loop или даже весь harness. Сегодня же мы посмотрим только на оптимизацию промптов и сравним два подхода: PromptWizard и MIPROv2.
MIPROv2
MIPROv2 (Multi-prompt Instruction Proposal Optimizer v2) — один из оптимизаторов в DSPy.
Если сильно упростить, его задача состоит в том, чтобы найти комбинацию инструкции и few-shot примеров, которая дает лучший результат на вашей задаче.
Для этого MIPROv2 сначала анализирует train-датасет и пытается понять какие инструкции и примеры вообще могут помочь модели решать задачу лучше. После этого через LLM генерируется большое количество кандидатов инструкций и наборов few-shot примеров.
Дальше начинается самое интересное.
Полный перебор всех комбинаций инструкций и примеров был бы слишком дорогим, поэтому MIPROv2 использует байесовскую оптимизацию. На каждой итерации фреймворк выбирает наиболее перспективных кандидатов, запускает их на train/validation выборке, измеряет качество по заданной метрике и использует полученные результаты для выбора следующего набора кандидатов.
По сути MIPROv2 пытается ответить на вопрос:
Если у меня есть ограниченный бюджет на эксперименты, какие варианты промптов стоит проверить следующими, чтобы быстрее найти лучший результат?
В результате вместо ручного перебора десятков инструкций этот цикл выполняется автоматически.
PromptWizard
PromptWizard — фреймворк от Microsoft, который использует совершенно другой подход.
Если MIPROv2 больше похож на поиск лучшей инструкции среди большого количества кандидатов, то PromptWizard делает ставку на последовательное улучшение инструкции через feedback loop.
Вместо того чтобы генерировать сотни вариантов и выбирать лучший, PromptWizard пытается понять почему текущий промпт работает плохо и как его можно улучшить.
В его основе лежат три ключевые идеи:
1. Итеративная оптимизация промпта через feedback loop.
2. Оптимизация инструкции и few-shot примеров одновременно.
3. Автоматическое построение reasoning traces для примеров.
Упрощенно цикл выглядит следующим образом:
1. Генерируется инструкция.
2. Инструкция запускается на датасете.
3. LLM анализирует ошибки.
4. LLM предлагает улучшения.
5. Создается новая версия инструкции.
6. Цикл повторяется.
Фактически модель выступает одновременно в роли исполнителя, критика и автора следующей версии промпта.
Из-за этого PromptWizard больше похож не на search-задачу, как MIPROv2, а на self-refinement цикл, где каждая новая версия инструкции строится на основе ошибок предыдущей.
В следующей части посмотрим как агенты могут оптимизровать/переписывать других агентов или самих себя
👍4