На протяжении десятилетий статическое тестирование безопасности приложений (SAST) было одним из самых эффективных способов, позволяющих командам безопасности масштабировать проверку кода.
Но когда мы создавали Codex Security, мы сознательно выбрали другой подход: не начинать с импорта отчёта статического анализа и не поручать агенту его триаж. Мы спроектировали систему так, чтобы она начинала с самого репозитория — его архитектуры, границ доверия и предполагаемого поведения — и проверяла находки, прежде чем отвлекать ими человека.
Причина проста: самые сложные уязвимости обычно связаны не с потоками данных. Они возникают, когда код как будто выполняет проверку безопасности, но на деле не гарантирует то свойство, на которое опирается система. Иначе говоря, задача не только в том, чтобы проследить путь данных по программе, — нужно понять, действительно ли работают защитные механизмы в коде.
SAST часто описывают как понятный конвейер: найти источник недоверенных данных, отследить путь данных по программе и отметить случаи, когда эти данные доходят до опасного места без санитизации. Это элегантная модель, и она охватывает множество реальных ошибок.
На практике SAST приходится идти на упрощения, чтобы его можно было применять в большом масштабе, — особенно в реальных кодовых базах с косвенными вызовами, динамической диспетчеризацией, колбэками, рефлексией и сложным управлением потоком во фреймворках. Это не недостаток SAST, а неизбежное следствие попытки рассуждать о коде без его выполнения.
Это само по себе не является причиной, по которой Codex Security не начинает с отчёта SAST.
Более глубокая проблема заключается в том, что происходит после того, как вы успешно отследите путь от источника к приемнику.
Даже когда статический анализ корректно отслеживает входные данные через несколько функций и уровней, ему всё равно нужно ответить на вопрос, который на самом деле определяет, существует ли уязвимость:
Возьмём распространённый паттерн: код вызывает что-то вроде sanitize_html() перед тем, как отрендерить недоверенный контент. Статический анализатор видит, что санитайзер сработал. Но обычно он не может ответить, достаточно ли этого санитайзера для конкретного контекста рендеринга, шаблонного движка, правил кодирования и последующих преобразований.
Вот тут-то и начинается самое непростое. Проблема не только в том, достигают ли данные приемника. Вопрос в том, ограничивают ли проверки в коде значение так, как система предполагает.
Иными словами: есть большая разница между “код вызывает санитайзер” и “система безопасна.”
Вот шаблон, который постоянно встречается в реальных системах.
Веб-приложение получает JSON-пакет, извлекает redirect_url, проверяет его по регулярному выражению списка разрешённых, декодирует URL и передаёт результат обработчику перенаправления.
Классический отчёт по цепочке «источник — приёмник» может описать такой поток:
недоверенный ввод → проверка регулярным выражением → URL-декодирование → перенаправление
Но настоящий вопрос не в том, существует ли эта проверка. Вопрос в том, продолжает ли эта проверка ограничивать значение последующих преобразований.
Если проверка регулярным выражением выполняется до декодирования, действительно ли он ограничивает декодированный URL так, как его интерпретирует обработчик перенаправления?
Чтобы ответить на этот вопрос, нужно разобрать всю цепочку преобразований: что допускает регулярное выражение, как работают декодирование и нормализация, как парсер URL обрабатывает пограничные случаи и как логика перенаправления разбирает схему и адрес назначения.
Многие уязвимости, которые имеют значение на практике, выглядят так: ошибки в порядке выполнения операций, частичная нормализация, неоднозначности при разборе и несоответствия между проверкой и интерпретацией. Поток данных виден. Слабое место — в том, как ограничения распространяются или не распространяются по цепочке преобразований.
Это не просто теоретический пример. В CVE-2024-29041(открывается в новом окне) в Express была проблема открытого перенаправления: некорректно сформированные URL могли обходить типичные проверки по списку разрешённых из-за того, как цели перенаправления кодировались, а затем интерпретировались. Сам поток данных был очевиден. Гораздо сложнее — и именно это определяло наличие уязвимости — было понять, сохраняла ли проверка силу после всей цепочки преобразований.
Codex Security строится вокруг простой цели: сократить объём триажа, показывая находки с более сильной доказательной базой. В продукте это означает использовать контекст репозитория, включая модель угроз, и проверять наиболее убедительные находки в изолированной среде, прежде чем показывать их человеку.
Когда Codex Security сталкивается с участком кода, который выглядит как валидация или санитизация, он не воспринимает это как формальную отметку «проверка пройдена». Система пытается понять, какую гарантию хотел обеспечить код, а затем — опровергнуть эту гарантию.
На практике это, как правило, выглядит как сочетание:
- Чтение релевантного пути выполнения кода с полным контекстом репозитория, как это сделал бы исследователь в области безопасности, и поиск несоответствий между намерением и реализацией. Сюда входят комментарии, но модель не обязательно верит комментариям, поэтому добавление //Halvar says: this is not a bug над вашим кодом не собьёт её с толку, если там действительно есть баг.
- Сведение проблемы к минимальному тестируемому фрагменту (например, к конвейеру преобразований вокруг одного входного значения), чтобы можно было рассуждать о ней, не отвлекаясь на остальную систему. В этом смысле Codex Security выделяет небольшие фрагменты кода, а затем пишет для них микро-фаззеры.
- Рассуждения об ограничениях на всём протяжении преобразований, а не рассмотрение каждой проверки по отдельности. Где это уместно, задачу можно формализовать как задачу на выполнимость. Иными словами, мы даём модели доступ к среде Python с z3-solver, и при необходимости она уверенно использует этот инструмент — так же, как это сделал бы человек, разбирая особенно сложную задачу с ограничениями на входные данные. Это особенно полезно при анализе целочисленных переполнений и схожих ошибок на нестандартных архитектурах.
- По возможности — выполнение гипотез в изолированной среде валидации, чтобы отделить «это может быть проблемой» от «это действительно проблема». Нет лучшего доказательства, чем полный PoC от начала до конца с кодом, скомпилированным в режиме отладки.
Это ключевой сдвиг: вместо того чтобы останавливаться на «проверка существует», система продвигается к «инвариант выполняется (или нет), и вот доказательства». И модель выбирает лучший инструмент для этой задачи.
Разумная реакция: почему бы не сделать и то и другое? Сначала взять отчёт SAST, а потом дать агенту разобраться глубже.
Бывают случаи, когда предварительно вычисленные результаты полезны — особенно для узких, известных классов ошибок. Но для агента, предназначенного для выявления и проверки уязвимостей в контексте, начало с отчёта SAST создаёт три предсказуемых режима сбоя.
Во-первых, это слишком рано сужает область поиска. Список находок — это карта того, где инструмент уже успел посмотреть. Если брать её за отправную точку, система начнёт тратить непропорционально много усилий на те же области и те же абстракции — и упустит классы проблем, которые не укладываются в модель самого инструмента.
Во-вторых, это может вносить неявные оценки, от которых потом трудно избавиться. Многие находки SAST уже содержат предположения о санитизации, валидации или границах доверия. Если эти предположения неверны — или просто неполны, — подача их в контур рассуждений может сместить агента из режима «разобраться» в режим «подтвердить или опровергнуть», а это не то, чего мы от него хотим.
В-третьих, это может затруднить оценку системы рассуждений. Если конвейер начинается с результатов SAST, становится сложно отделить то, что агент обнаружил в результате собственного анализа, от того, что он унаследовал от другого инструмента. Это разделение важно, если вы хотите точно измерять возможности системы, что необходимо для того, чтобы система могла со временем совершенствоваться.
Поэтому мы построили Codex Security так, чтобы он начинал там, где начинается исследование безопасности: с кода и предполагаемого поведения системы, а затем использовал валидацию, чтобы повышать планку уверенности, прежде чем отвлекать человека.
Инструменты SAST хорошо справляются с тем, для чего они предназначены: помогают соблюдать стандарты безопасного кодирования, находят прямолинейные уязвимости по схеме «источник — приёмник» и выявляют известные паттерны в большом масштабе с понятными компромиссами. Они могут быть важной частью многоуровневой защиты.
Этот текст о более узком вопросе: почему агент, который должен рассуждать о поведении системы и проверять находки, не должен начинать работу с привязкой к статическому списку результатов.
Стоит также отметить связанное с этим ограничение чисто «от источника к приемнику» мышления: не каждая уязвимость является проблемой потока данных. Многие реальные сбои связаны с проблемами состояния и инвариантов — обходами рабочих процессов, пробелами в авторизации и ошибками вида «система находится в неверном состоянии». Для этих типов ошибок загрязнённое значение не достигает ни одного «опасного приемника». Риск заключается в том, что программа предполагает, что всегда будет верным.
Мы ожидаем, что экосистема инструментов безопасности будет продолжать улучшаться: статический анализ, фаззинг (нечеткое тестирование), защита во время выполнения и агентные рабочие процессы — все будут играть свою роль.
Мы хотим, чтобы Codex Security лучше всего справлялся с тем, что обходится командам безопасности дороже всего: превращать «это выглядит подозрительно» в «это реально, вот как это ломается и вот исправление, которое соответствует назначению системы».
Если вы хотите узнать больше о том, как Codex Security сканирует репозитории, проверяет результаты и предлагает исправления, см. нашу документацию(открывается в новом окне).


