# От мешка false positive до нормального вердикта: паяем ZeroFalse-inspired LLM-триаж на локальных моделях ## Метаданные Доклад: «От мешка false positive до нормального вердикта: паяем ZeroFalse-inspired LLM-триаж на локальных моделях» Спикер: Юрий Туманов / Эльария / Psycho Drake ## Отредактированный текст ### Вступление Ребята, всем привет. Позвольте мне представить себя как человека, который полгода гонял локальную модель на домашней RTX 4080 для LLM-триажа. И у него получилось. Чуть-чуть о себе и о том, почему вообще эта тема. Так сложилось, что на собеседовании я, можно сказать, «обманулся»: скачал себе LLM, немного потрогал, мне понравилось. Вообще видеокарта покупалась для того, чтобы играть в Cyberpunk. А сейчас я отвечаю на вопрос, можно ли с ее помощью устроить «киберпанк» в реальной жизни. И, кажется, это получается. ### SAST и боль false positive Немного о SAST и о нашей боли. Статический анализ обычно производится автоматизированными статическими анализаторами. Они ходят по коду и говорят: вот здесь у нас подозрение на такой дефект, здесь — на такой. Все это складывается в агрегатор или концентратор. А дальше в концентраторе нужно понять: это реальное срабатывание или, скорее всего, нет? До этого такой triage в основном делали люди. И люди смотрели на находки примерно с мыслью: «Ну ты же врешь». Потому что, по статистике, у кого ни спроси, от 95 до 98% SAST-срабатываний оказываются false positive. Мы не хотим, чтобы у людей кровоточили глаза, поэтому тема сейчас достаточно востребована. Мы как раз в нее погружаемся. ### Что можно забрать с собой Немного спойлеров: что вы узнаете и что потом можно будет забрать с собой. Во-первых, это базовые тезисы, которыми стоит руководствоваться при работе с LLM-подходами, особенно на небольших моделях. Идея с доработкой небольших prompt’ов для малых моделей действительно важна. У меня дома на RTX 4080 поднимается контекст до 8192 токенов. Это не так много: полный trace при какой-нибудь «суперпрожорливой» находке туда можно и не загнать. Поэтому приходится изобретать. Я этим занимаюсь, и мы в том числе этим занимаемся. Я расскажу о нескольких тезисах, к которым пришел, и о том, как я к ним пришел. Это тоже полезно. Отправной точкой для меня была статья про ZeroFalse, вышедшая в конце октября 2025 года: там авторы донастраивали большие модели и получали хорошие результаты. Я подумал: у меня модель меньше, железо меньше, но почему бы не попробовать? В ближайшие 15 минут я расскажу, как пришел к этим выводам, и немного покажу результат. ### От экспериментов к стенду Начиналось все довольно просто. Когда я только разбирался, я спрашивал у ChatGPT что-то в духе: «У меня вот такая конфигурация. Как мне не в Cyberpunk поиграть, а LLM запустить?» Он отвечал: «Делай так». Я делал: «Окей». Так это начиналось примерно год назад. Полгода назад я уже понимал, как устраивать inference у себя локально. Другое дело, что мне были нужны специфические штуки. Сначала я собрал ансамбль примерно из десяти моделей, подавал на них batch-задачи, которые сам отбирал, и дальше эвристически и статистически оценивал: оно нормальное или нет, заходит или не заходит. То есть первый вопрос был вообще простой: работает ли это в принципе? К текущей модели на своем железе я пришел эмпирически. Было сравнение с Qwen3, но почему-то на тех же задачах он показывал себя хуже. Возможно, это как раз связано с тем, что общий подход можно брать один, но конкретные prompt’ы под разные модели уже отличаются. Если вы меняете модель, особенно маленькую, prompt’ы, скорее всего, тоже придется менять. Текущая рабочая конфигурация: локальный inference через vLLM, модель Qwen2.5-Coder-14B-AWQ, видеокарта RTX 4080 с 16GB VRAM. По времени это примерно 3-6 секунд на одно решение, то есть на один акт triage. Почему вообще небольшие локальные модели? Потому что выход за корпоративный периметр невозможен: нельзя отправлять наружу внутренний код, trace’ы и все такое. ### Текущий pipeline На слайде был представлен текущий pipeline. Организаторы еще подкидывали идею, как его расширить; возможно, будем расширять, и потенциально это будет здорово. Пока все работает достаточно тривиально. Мы берем лишние данные из агрегатора и сначала прогоняем pre-check регулярками и эвристиками. Это важно: по крайней мере в некоторых классах дефектов такой слой повышает продуктивность примерно на 30%, потому что часть уязвимостей или дефектов можно разобрать эвристически. Например, это хорошо работает с какими-то hardcoded secrets. Регулярки помогают не только отсеивать базу, но и формировать input для небольшой локальной модели, уточняя контекст. Часто можно сразу отбросить совсем базовые false positive вроде условных `YALA_TOKEN`, лежащих в фигурных кавычках. Дальше LLM выполняет triage. Она принимает на вход prompt из trace’а агрегатора, возможно, результат heuristic pre-check, и данные по rule. После этого она должна вернуть результат строго в JSON. JSON — отдельный важный момент. Если модель не умеет нормально работать с JSON, то пусть она даже думает хорошо и рассказывает что-то убедительное, но каши с ней не сваришь. Что вы будете делать с ее философией на месте строительства? Нужен машинно обрабатываемый verdict. Дальше мы принимаем вердикт и отправляем его обратно в агрегатор. ### Почему prompt короткий, а контекст важный С чего все начиналось? С первой версии prompt’а. Почему prompt’ы короткие? Модель — это быстрый, но болтливый джун. Prompt обогащает контекст, и за счет этого можно добиваться от модели реальных решений. Но формирование конкретного контекста для конкретной модели — это немного искусство и немного ремесло. Каждая модель в этом плане индивидуальна, даже если она coder-модель. Универсального подхода к формированию prompt’ов нет. В общем случае задача неразрешима «один раз и навсегда». Можно следовать общим подходам, но на этом работа не заканчивается. Первая версия начиналась с hardcoded credentials. Модель видела raw data и начинала считать хардкодом вообще все: большие числа, константы и прочее. С этого момента начались маленькие prompt’ы и постепенная эволюция. ### Эвристики перед LLM Первый большой тезис: heuristic pre-check очень полезен для моделей. Модели можно помогать структурой. На основании эвристик, pre-check’ов и raw data, который передается в модель, можно не задавать ей вопрос «что ты об этом думаешь?», а давать только релевантные маркеры и строить логику: «если видишь такое-то, думай вот так». То есть мы не заставляем модель самой выдумывать всю цепочку. Мы подсказываем, какие признаки важны и как на их основании принимать решение. ### Ошибка в марте и downgrade вместо false positive В марте случился большой косяк. Казалось, что модель работает качественно, но в какой-то момент выяснилось: не очень. Один секрет оказался со статусом false positive, хотя таковым не был. Здесь есть важное замечание. Если вы меняете политику или меняете часть prompt’ов, часть старых prompt’ов придется выкидывать. У нас был момент, когда сначала политика звучала так: если это не hardcoded secret, то считаем false positive. На этом и поймали проблему. Сейчас в таких случаях у нас идет не автоматическое объявление false positive, а понижение критичности или приоритета срабатывания. То есть если модель не уверена или признаки не дотягивают до подтверждения, мы скорее downgrade’им finding, чем закрываем его как false positive. ### Unknown, калибровка и ground truth Еще один важный момент — статус unknown. Модели часто сомневаются. Если у них недостаточно входных данных, то даже большие модели могут честно не вывезти: «Я на 90% не знаю, этому не училась». Системный prompt, конечно, нужен, но сам по себе он не всегда спасает. Обязательный процесс — регулярная калибровка модели и prompt’ов. При этом внутренние данные нельзя отдавать наружу. Но можно делать иначе: брать результаты срабатываний, формулировать обобщенные случаи и думать, как улучшить system prompt и остальные prompt’ы. Для этого нужен размеченный ground truth: вручную размеченный эталон, которому вы доверяете. В нашем случае речь идет о 245 вручную размеченных эталонах. Ground truth — это то, что мы считаем истиной. Человек есть мера всех вещей: если мы называем это истиной, значит это и есть истина для нашей оценки. Если модель начинает соглашаться с результатами человеческого triage, мы считаем, что она ведет себя хорошо. ### Разделение классов дефектов Дальше мы пришли к разделению классов. Для принятия решений по XSS, SQL injection и XXE часто нужно много raw data: код, trace, контекст вызовов. Иногда какие-то куски не доходят или, наоборот, доходит недостаточно кода, и модель начинает сомневаться. Поэтому мы создаем эвристики на основании кода: если видишь X, Y и Z, и они находятся в такой форме, то склоняй решение в сторону `confirmed` или, наоборот, не в сторону false positive. Значения этих эвристик получены не «от балды». Это результат retriage по ground truth: повторного анализа того, что считается истинным срабатыванием. Качество постепенно сдвигалось от «вообще ничего не знает» и `unknown` к уверенным совпадениям. ### Пример с weak hash Отдельно был пример с weak hash. Это одновременно гибрид двух проблем: мы не проследили за политиками, и пришлось менять часть prompt’ов, чтобы получить требуемый результат. Речь идет о policy: политики в организации могут отличаться от политик у вас. Например, что делать с использованием MD5, SHA-1 или других weak hash, относящихся к CWE-328. Где-то это надо фиксить всегда, где-то зависит от контекста: это криптография, контрольная сумма, совместимость с внешним протоколом или что-то еще. За prompt’ами и политиками нужно следить и совмещать их. Иначе модель может формально делать то, чему вы ее научили, но это уже не будет соответствовать текущей внутренней политике. ### Результаты и small independent benchmark В итоге удалось получить модель, которая корректно распознает истинность на наборе истинных срабатываний и false positive. В оценке мы сравнивали результат модели с человеческим ground truth. На small independent benchmark было 88 решений: половина false positive, половина confirmed. На слайде показывалось agreement — согласие модели с человеческой разметкой. Часть расхождений оставалась, но некоторые из них были примерами того, что модель оказалась права, а человек — нет. Возможно, этим человеком был я. Главный вывод: это нужно еженедельно пересматривать. Weekly calibration, просмотр расхождений, докапывание до причин — рутина, с которой придется жить. ### Как выглядит triage У меня есть видео того, как происходит triage. У нас не красивый GUI, а командная строка, но зато все работает. На видео можно было наблюдать запуск команды, начало inference и дополнительный context, который передается модели. Дальше модель какое-то время думает, крутится progress bar, а потом выдает ответ. Она выдает не только `confirmed`, но и объяснение: какой статус присвоить issue, почему, и что делать разработчикам. Потенциально мы думаем, как это потом использовать для автоматизации описаний для разработчиков. Также в результате сессии видны даты, токены и прочая служебная информация. Статистика отправляется в базу, чтобы за процессом можно было наблюдать и потенциально делать повторный triage. Обратная связь в таких процессах обязательна. ### Что важно запомнить Повторю основные мысли. Зачем трогать задачу эвристиками до LLM? Чтобы не кормить модель лишней информацией. Лишняя информация действительно лишняя: она мешает и тратит контекст. JSON schema нужна для повышения качества и пригодности результата. Модель должна выдавать не философию, а структурированный ответ, с которым дальше может работать pipeline. Эвристики вокруг LLM помогают ускорить процесс triage и откидывать некорректные срабатывания на pre-check. И обязательно нужен эталон, которому вы доверяете. Его можно прогонять снова и снова, делать retriage, сравнивать человека с моделью и постепенно получать более solid base для прокачки prompt’ов. Я общался с начальством, и кое-чем смогу поделиться: есть система про эволюцию prompt’ов и результаты по некоторым CWE. Постараюсь выложить что-то более интересное. Если кому-то будет интересно, контакты есть, можно будет со мной связаться. В начале я говорил, на основании чего сформулировал эти тезисы. Иногда за деревьями теряешь лес. Я, не залезая в приватные данные, дал модели посмотреть на pull request’ы, issue и prompt’ы в своем процессе. И модель сказала: «Смотри, чувак, ты добился вот этого», — и перечислила, что и почему. Я ответил примерно: «Я это сделал? Мне так плохо». Потому что я еженедельно делал эту рутину и видел, что она повышает качество в числах, но часть причинно-следственных связей осознал уже потом. Спасибо за внимание. ### Вопросы и ответы Вопрос: Если я правильно понял, речь шла о локальном разворачивании? Ответ: Да. Я поднимал все локально на RTX 4080. Вопрос: Как лучше обучать модель? Ответ: Я прошу прощения: что вы понимаете под «обучать»? На этот вопрос я не готов сразу ответить, потому что я ее не обучал. Я выбирал из присутствующих на рынке моделей. Мне интересно покопаться с обучением и, например, с LoRA, это у меня есть в backlog. Надеюсь когда-нибудь посмотреть, но сейчас компетентных данных у меня нет. Вопрос: Пробовали ли вы аналогичные uncensored-модели? Ответ: Да. По моему опыту — плохо. Я пробовал их, например, для генерации exploit’ов. Логика у них вроде бы присутствует, но результаты часто не запускаются или выглядят галлюцинациями. Я ходил с этим к чатам, которые нормально относятся к таким вопросам, и смотрел: здесь есть какая-то логика, но оно не будет работать. Возможно, нужны более «думающие» модели, возможно, их надо именно обучать под задачу. Но для текущего pipeline им еще нужно стабильно работать с JSON, а это обязательное требование. Вопрос: Uncensored-модели сами по себе часто сломанные? Ответ: Да, часто сложно найти что-то рабочее. Uncensored-модель нужно сразу проектировать такой. Наличие safeguard’ов делает модель в чем-то тупее, но когда вы просто убираете safeguard’ы, вы нарушаете состояние весов. Это все влияет на веса модели. Вопрос: Как начальство относилось к тому, что закрытая инфраструктура и критичная информация передаются модели, про которую непонятно, что она делает? Ответ: Модель разворачивается локально. Вопрос: Но что в этой модели с точки зрения безопасности? Ответ: Когда вы запускаете модель, у нее нет storage. Модель — это веса. Она не обучается волшебным образом на данных, которые вы ей передаете. Чтобы обучать модель, нужно как минимум провести LoRA или другую процедуру дообучения. К моему железу могут быть гипотетические вопросы — это одна тема. Но в рамках модели данные просто передаются на inference: она подумала, выдала ответ, inference закончился. Вопрос: Скорее вопрос в том, как это объяснить начальству. Ответ: Мое непосредственное начальство это санкционировало. Я, собственно, на работу и устраивался потому, что сказал: я делаю triage на локальных моделях. Им это было интересно. Вопрос: Сколько это занимает памяти для локальных моделей? Можно ли смотреть в сторону другого железа, например Apple или специализированного железа? Ответ: Сейчас идет сбор репрезентативных данных, на которых можно будет сказать: «Купите мне Apple, купите мне RTX 5080, купите мне что-то классное». Но железо стоит космос, и его покупку нужно обосновывать. Если бы мы собирались проводить inference, условно, на «Байкалах», которые тоже тайваньские, это был бы отдельный разговор. Но в целом сначала нужны данные, которыми можно обосновать покупку. Вопрос: Насколько длинные были сессии? Ответ: Здесь нет накапливаемого memory/state. Это inference по stateless-подходу на vLLM. Каждый новый запрос — это новый запрос. Возможно, из-за этого получается не так быстро. Я оставлял компьютер включенным на ночь, и там проводилось около пяти тысяч triage’ей. В последнее время стало медленнее, потому что обвязка выросла: prompt’ов стало больше, они стали длиннее, а скорость зависит и от длины контекста. Максимальный контекст, который я у себя сумел растянуть на RTX 4080, — 8192 токена. Это максимум, который у меня взлетает. Такая жизнь. ## Неоднозначные места - В начале доклада ASR искажает фразу про «полгода локальную модельку на домашней 4080 на LLM 3H». В редактуре восстановлено как «полгода гонял локальную модель на домашней RTX 4080 для LLM-триажа». - Название компании/контекста «Цезарус» в ASR не подтверждено слайдами. В основном тексте оно опущено, потому что для смысла доклада достаточно SAST-контекста. - В исходнике несколько раз звучит «правды/промты/правдов». По контексту почти везде восстановлено как prompt’ы; там, где речь о политике, оставлено как policy/политики. - Фраза про «D-23 rule» в сырой ASR явно не читается. Термин есть в предоставленном контексте, но без надежного места в речи, поэтому в основной текст он не вставлен. - Метрики recall/precision в исходнике неразборчивы: понятно, что докладчик показывал agreement и сравнение с ground truth на small independent benchmark, но конкретные значения не восстановлены. - Упоминание «CWE-328 weak hash» восстановлено по контексту слайдов и месту про weak hash/policy. Конкретные примеры алгоритмов в речи звучали нечетко, поэтому приведены как типичные MD5/SHA-1 с оговоркой «например». - Упоминание «CWE-798 hardcoded credentials» в транскрипте проявляется как блок про hardcoded secrets/credentials, но номер CWE в речи не был надежно распознан. - В Q&A несколько фрагментов про альтернативное железо и «Байкалы» сильно зашумлены; смысл сохранен как обсуждение обоснования покупки железа, без попытки восстановить все названия. - Последняя фраза про «макер минут ... 30 миллиардов параметров» не восстановлена: в основной текст не включена.