Часть 1 этой серии завершилась на высокой ноте. Модель Antares-1B от Cisco, запущенная на моем ноутбуке, направляет луч на репозиторий и говорит «посмотри сюда», и примерно в одном случае из трех она указывает на правильный файл. Дешево, конфиденциально, быстро.
Она также завершилась неприятной правдой: «посмотри сюда» не означает «это реально».
Этот разрыв — не погрешность округления. Это вся дистанция между демо и программой безопасности. И этот разрыв не устраняется сам по себе за счет более крупной модели. Попросите лучшую из доступных LLM «найти уязвимости в этом коде», и вы получите уверенный, хорошо отформатированный ответ. Чего вы не получите, так это какого-либо способа узнать, какие из этих утверждений были проверены, получите ли вы тот же ответ в следующий вторник или почему конкретная находка существует спустя шесть месяцев, когда об этом спросит аудитор.
Поэтому данная статья посвящена кузнице. Я взял спецификацию защитного каркаса Foundry, опубликованный Cisco проектный документ (а не инструмент), создал его рабочую реализацию, а затем сделал то, что действительно важно: применил его к реальному коду. Не к игрушечному приложению с заложенными багами. К двум крупным, популярным платформам AI/ML, обучающему приложению OWASP и трем известным библиотекам на Python с опубликованными CVE.
Вот что произошло, что это значит для вас и как применить это к вашему собственному коду. Все, что приведено ниже, взято из сохраненных запусков в моем репозитории Foundry.
Коротко в одном предложении
Foundry не делает модель умнее. Она делает вывод модели тем, на чем можно построить процесс.
Это звучит как шаг назад, пока вы сами не попытаетесь внедрить инструмент безопасности на базе LLM в production. Модель никогда не была узким местом. Узким местом было доверие.
Foundry — это уровень управления (governance) поверх любой модели, которую вы уже используете. Конвейер состоит из четырех ролей, код которых достаточно прост, чтобы изучить его за полдня:
- Индексатор (Indexer) разбивает код на единицы на уровне функций.
- Детектор (Detector) спрашивает модель, какие правила безопасности применимо к каждой единице, а также выполняет легкий исследовательский обход для всего, что не описывает ни одно правило.
- Триажер (Triager) выносит вердикт по каждому кандидату со структурированными цитатами: файл, строка и точный процитированный текст.
- Репортер (Reporter) присваивает каждой находке стабильную идентичность, сортирует по степени критичности и визуализирует результат.
Вокруг всех четырех компонентов находится Оркестратор (Orchestrator) с жестким бюджетом токенов, заявленным условием остановки, журналом происхождения (provenance log) с возможностью только добавления и хранилищем, которое выполняет дедупликацию.
Два дизайнерских решения имеют наибольший вес.
Обнаружение является инклюзивным; решение принимается строго. Детектору поручено отдавать предпочтение полноте (recall): предлагать все правдоподобное. Вся дисциплина находится на последующих этапах. Такое разделение позволяет одной модели быть одновременно тщательной и надежной, а не заставлять вас выбирать что-то одно.
Шлюз доказательств — это не второе мнение. Он никогда не спрашивает модель: «Ты уверен?». Он берет каждую цитату и сверяет ее с байтами на диске: файл должен существовать внутри цели, строка должна находиться в нужном диапазоне, а процитированный текст действительно должен там присутствовать. Находка получает метку «true-positive» (истинно положительная), только если ее цитаты прошли проверку. Все остальное понижается до статуса «needs-review» (требует рассмотрения) либо отклоняется и никому не показывается.
Модель не может заставить принять несуществующий номер строки с помощью споров.
Реальный мир, остановка первая: Langflow
Первой целью стал Langflow 1.7.3, популярная low-code платформа для создания агентов и конвейеров LLM. Я выбрал ее, потому что она реалистична: большая кодовая база на Python от первого лица с реальной поверхностью API, реальной логикой авторизации и реальной историей. Область охвата включала весь API v1: 289 функциональных единиц в 25 модулях.
И я установил для себя жесткое правило для этого эксперимента: никакой поддельной базовой линии (baseline) для отвода глаз.
Здесь было бы легко сфабриковать победу. Направить усеченный промт LLM на код, позволить навороченной системе победить его и объявить о победе. Это ничего не доказывает. Поэтому базовая линия получила ту же модель, тот же код и честный, дисциплинированный промт для старшего специалиста по AppSec, требующий именно той строгости, которую обеспечивает каркас: структурированные находки, точные цитаты строк, буквальные цитаты, отсутствие вымышленных ссылок, отсутствие дубликатов. Я зафиксировал методологию перед запуском.
О чем говорят цифры
Три честных прочтения этой таблицы.
Foundry обнаружила более чем в два раза больше находок. Та же модель, тот же код. Не потому, что она умнее, а потому, что один шаг агента формирует короткий список заголовков и отбрасывает так называемый «длинный хвост», в то время как каркас прочесывает каждую из 289 единиц в рамках заявленного бюджета. Уязвимость в модуле 23 из 25 важна так же, как и уязвимость в модуле 1. Полнота — это свойство каркаса, а не модели.
Точность цитирования совпала. Способная модель, к которой добросовестно обратились с требованием строгости, в основном обеспечила ее. Но посмотрите, почему это совпадение менее утешительно, чем кажется: чистые цитаты базовой линии являются свойством именно этой модели, в этом масштабе и с этим промтом. Замените ее на более слабую модель или менее аккуратный промт, и все молчаливо сломается. 100% результат Foundry гарантирован самой конструкцией, поскольку один и тот же шлюз срабатывает при каждом запуске.
Уровень уверенности — вот реальное отличие. Базовая линия назвала 14 из 15 своих находок «true-positive». Ничто это не подкрепляло. Это лишь надежда на то, что модель сама выразит свое сомнение. Граница Foundry между подтвержденными находками и теми, что требуют рассмотрения, была проведена механической проверкой, а не настроением модели.
Как выглядит одна проверенная находка
Самой серьезной проблемой, обнаруженной во время запуска, стала критическая ошибка внедрения кода (code-injection). Langflow берет отображаемое имя входных данных потока, которые контролирует пользователь, и вставляет их в строку исходного кода Python, которую затем выполняет с помощью функции exec(). Единственная очистка заключается в переводе в нижний регистр и замене пробелов на подчеркивания, что никак не влияет на круглые скобки, двоеточия или символы новой строки.
Простой агент может обнаружить это. Самое интересное — это то, что окружает находку:
- Три процитированные строки, показывающие, как пользовательский ввод становится исходным кодом, исходный код становится функцией, а функция выполняется. Шлюз прочитал все три строки в реальном файле, прежде чем находке было разрешено получить статус подтвержденной.
- Постоянная идентичность, созданная на основе файла, класса уязвимости и символа, намеренно не привязанная к номеру строки, потому что номера строк имеют свойство смещаться.
- Указатель в журнале происхождения, ведущий обратно к точному вызову детектора, который ее предложил, и точному вызову триажа, который ее подтвердил, с прикрепленными данными об использовании токенов. Кто-то, кого не было рядом, сможет восстановить причину появления этой находки шесть месяцев спустя.
При том же запуск также подтвердил две критические уязвимости обхода пути (path traversal) в API базы знаний, где пользовательский ввод может выполнять произвольное удаление каталогов.
Запустите дважды
Вот тот режим сбоя, который тихо убивает инструменты безопасности на базе LLM. Вы запускаете его в понедельник, проводите триаж результатов, запускаете в пятницу для следующего коммита и получаете сформулированную по-новому стену текста, которую теперь приходится сравнивать вручную. После второго раза команда перестает запускать инструмент.
Поэтому я запустил полный конвейер дважды с независимыми вызовами моделей. В ходе обоих запусков хранилище содержало 122 записи о находках, которые сократились до 67 уникальных отпечатков. 56 повторились и закрепились за своими существующими идентификаторами вместо дублирования: примерно 90% пересечения несмотря на недетерминированность модели. Находки имеют одинаковый отпечаток при обоих запусках.
Этот второй запуск моделирует ваш следующий CI-запуск. От него вам нужен дельта-анализ (новые, повторяющиеся, исчезнувшие проблемы), вычисленный автоматически, а не на глаз. Один шаг агента не имеет постоянства между запусками, поэтому вычислять просто нечего.
Шире охват — больше разрыв
Я также запустил более узкий срез Langflow, состоящий из 12 модулей и 107 единиц. При таком масштабе базовое решение в один проход не уступало по охвату. «Сырые» показатели выглядели как ничья, и только свойства верификации разделяли эти два подхода. При полном охвате в 25 модулей базовая модель просто не могла удержать все в одном контексте и отобрала около 15 основных пунктов.
Это самое полезное, что я усвоил из опыта с Langflow: ценность обвязки растет вместе с масштабом. Именно поэтому следующей остановкой стал более крупный проект.
Реальный мир, остановка вторая: MLflow
Беспристрастный скептик мог бы спросить, не подстроил ли я незаметно обвязку под Langflow. Поэтому я применил ее к MLflow 2.9.0, другой и гораздо более крупной кодовой базе, изменив только файл конфигурации: целевой путь, шаблоны путей (glob) и выходной каталог. Сам движок и корпус из 13 правил остались прежними.
Здесь выделяются две вещи.
Результаты совпали с тем, чего можно было ожидать исторически. 34 находки, связанные с обходом пути (path-traversal), сконцентрировались в слое хранения артефактов MLflow (локальном, FTP, SFTP, HDFS и репозиториях артефактов Databricks), а также в его утилитах для работы с файлами и URI. Это та же область, что и реальные CVE MLflow, связанные с обходом пути: CVE-2023-6909 и CVE-2023-6977. Правило, обнаружившее обход пути в API базы знаний Langflow, нашло его и в репозиториях артефактов MLflow без каких-либо изменений.
Большая часть обнаруженного попала в категорию «требует проверки», и это как раз то, ради чего существует этот уровень. На более крупной и незнакомой кодовой базе шлюз гораздо консервативнее подходил к тому, что разрешать модели называть подтвержденным. Обычный агент выдал бы вам 123 пункта с той же уверенной формулировкой. Foundry выдала 13 пунктов, которые она смогла доказать построчно, и 110 зацепок, четко разделив их. Для команды, решающей, куда направить часы ревьюеров, это разделение имеет принципиальное значение.
При этом средства верификации работали так же эффективно при трехкратном увеличении масштаба: шлюз доказательств, происхождение, ограниченная остановка — все вело себя точно так же, как и на Langflow. Один шаг агента не способен удержать 830 единиц в одном окне контекста. Обвязке это и не нужно.
Реальный мир, остановка третья: учебное приложение OWASP
PyGoat — это намеренно уязвимое приложение OWASP на Django. Оно небольшое, около 1 МБ, и содержит реальный код приложения: оно вызывает pickle.loads(), eval(), subprocess.Popen(shell=True) и «сырые» SQL-запросы к пользовательскому вводу. Это ровно те слабости на уровне паттернов, для которых создан корпус правил безопасности.
Полное сканирование его 180 единиц за примерно шесть минут выявило 45 находок: 42 подтвержденные, 3 требующие проверки, 14 с критическим рейтингом в 11 классах уязвимостей. Зашитые в код секреты (JWT, подписанный литеральной строкой, учетные данные в открытом виде), инъекция команд оболочки (shell injection), eval() для POST-ввода, хеширование паролей MD5, «сырые» SQL-запросы, собранные конкатенацией строк, pickle.loads() из cookie, SSRF. Каждая подтвержденная находка прошла через шлюз доказательств.
Как клиенты и партнеры Cisco могут это использовать
Все, что я показал, находится в публичном репозитории. Вот как это применить на практике.
Если у вас есть свой код, запустите инструмент на своем репозитории
Руководство по сборке из репозитория проведет вас через этот процесс, но структура проста, и каждый шаг сводится к изменению конфигурации, а не кода:
- Зафиксируйте целевую версию. Добавьте свой репозиторий и фиксированный тег или коммит в скрипт получения, чтобы каждый запуск был воспроизводимым, а каждая находка ссылалась на известное состояние дерева исходного кода.
- Сначала определите границы поверхности атаки. Установите шаблоны включения для кода, который обрабатывает внешний ввод: маршруты API, обработчики, представления (views), утилиты для работы с файлами и URL. Исключите тесты и миграции. Запишите, почему вы выбрали именно такие границы. Позже вы сможете их расширить.
- Сформулируйте свои цели простым языком. «Сосредоточьтесь на изоляции тенантов и авторизации, инъекции — второстепенны» или «Мы работаем с персональными данными (PII), приоритет — утечка данных и логирование секретов». Этот текст внедряется в промпты как детектора, так и триажера, поэтому он реально управляет оценкой.
- Установите бюджет. Выберите модель и жесткое ограничение по токенам. Контроллер аккуратно останавливается при достижении лимита. Вы никогда не получите неожиданный счет.
- Используйте собственные правила. Поставляемые 13 правил написаны на Python и представляют собой универсальный для фреймворков YAML. Добавляйте стандарты вашей компании таким же образом, и триажер будет проверять их со ссылками, а не на основе «интуиции». Сам движок не зависит от языка программирования; специфичен для Python только экстрактор функций.
- Запуск, оценка, повтор. Запустите обвязку, при желании запустите честную базовую модель и сравните результаты друг с другом, затем запустите снова на следующем коммите и проводите триаж только для дельты.
Мои цифры по Langflow дают грубую модель затрат: полное сканирование 289 единиц обошлось примерно в 2 млн токенов, а 180 единиц PyGoat — примерно в 420 тыс.
Если вы создаете программу безопасности: соберите свое решение по спецификации
Мой репозиторий — это эталонная реализация: учитель, а не продукт. Главный ценный актив — это сама спецификация Foundry: около 130 требований и три десятка осознанных решений с пометкой [NEEDS CLARIFICATION], которые она оставляет на ваше усмотрение, такие как хранилище данных, декомпозиция ролей, правило остановки и схема уровней критичности.
Пропустите спецификацию через такой инструмент, как spec-kit, примите решения для вашего контекста и используйте документацию по кастомизации и архитектуре из моего репозитория в качестве готового варианта ответов для каждого из них. Свойства доверия (шлюз, цифровые следы, происхождение, ограниченная остановка) — это то, что вы переносите с собой.
Если вы партнер: продавайте доказательства, а не мнения
Большинство проектов по «проверке кода с помощью ИИ» заканчиваются PDF-отчетом с результатами модели, который клиент должен принимать на веру.
Работа в стиле Foundry заканчивается совсем иначе: каждая подтвержденная находка привязана к процитированным строкам исходного кода, проверенным машиной; ведется журнал только для добавления (append-only) для каждого вызова модели, стоящего за каждым вердиктом; объем и бюджет согласовываются заранее; используются стабильные идентификаторы находок, поэтому повторная оценка в следующем квартале покажет клиенту измеримую дельту вместо новой стены текста. Это повторяемая, аудируемая услуга, которая работает на любой модели, использование которой уже одобрено клиентом.
Чего я не утверждаю
Foundry не является более умным детектором багов. Модель та же самая. Она находит больше, потому что проводит исчерпывающий перебор.
Foundry не дешевле. Расход токенов примерно в 10 раз превышает один шаг агента на Langflow. Эти затраты покупают покрытие, шлюз и дедупликацию при жестком ограничении. Это осознанный компромисс.
Нет концепции «ноль ложных срабатываний, ноль пропусков». Это отрицательные величины, которые нельзя доказать без идеальной эталонной базы. Здесь «истинно положительный» означает проверенный результат, стоящий времени ревьюера, а «требует проверки» означает зацепку.
Ничто здесь не помечено как «проэксплуатированное». Доказательство возможности эксплуатации требует живого тестового стенда, и спецификация справедливо относит это к отдельной роли, которую я не стал реализовывать. Документация по архитектуре упоминает этот пробел и его цену.
И когда же обычный агент — это правильный ответ? Когда один инженер единожды проверяет небольшой и знакомый кусок кода. Это гораздо дешевле и действительно полезно. Просто используйте его. Foundry окупает свои затраты, когда результаты должны поступать в рабочий процесс: в трекер задач, который не должен утонуть в дубликатах; к ревьюерам, которым нужно знать, что именно проверялось; при повторяющихся запусках по мере изменения кода.
Далее: Файл, который продолжает наполняться
Есть еще один артефакт, о котором я почти не упоминал, и он оказался самой ценной вещью, которую производит обвязка.
Каждый раз, когда исследовательский проход Detector находит что-то, не описанное ни одним правилом в корпусе, а Triager подтверждает, что это реально, Foundry записывает это в файл. В Langflow этот файл зафиксировал эндпоинт, который ограничивает себя только «локальными подключениями» и определяет, что такое «локальный», доверяя заголовку, который может установить любой злоумышленник. В MLflow он зафиксировал неконтролируемый индекс в пути, предоставленном пользователем, который в итоге определяет, на какой запуск влияет привилегированный вызов API.
Это не просто находки. Это уроки: паттерны, которые система не умела искать, извлеченные из реального кода.
Между тем ИИ-ассистенты программирования пишут новый код быстрее, чем любой из нас успевает его проверять, и они тоже не знают этих уроков. В Части 3 я покажу вам, как Foundry превращает каждый урок в правило, которое живет прямо в IDE разработчика, так что в следующий раз, когда ИИ-ассистент соберется написать этот баг, у него не получится.
До встречи на следующей неделе!
Все здесь воспроизводимо из : сохраненные запуски для каждой цели, журналы происхождения, пробелы в правилах и оцененное сравнение.











