Заглянули под капот Security Code Scanning Autofix от GitHub Advanced
В ноябре 2023 года GitHub объявил о запуске Code Scanning Autofix, который с помощью искусственного интеллекта предлагает исправления уязвимостей безопасности в кодовых базах пользователей. В этой статье мы расскажем о том, как работает Autofix, а также о системе оценки, которую мы используем для тестирования.
Что такое Code Scanning Autofix?
Code Scanning Autofix от GitHub анализирует код в репозитории, чтобы найти уязвимости в системе безопасности и другие ошибки. Сканирование может запускаться по расписанию или при наступлении определённых событий — таких как push или открытие пулл-реквеста. При обнаружении проблемы пользователь получает уведомление. Code Scanning можно использовать с инструментами уведомлений от первого или стороннего производителя, включая инструменты с открытым исходным кодом и частные инструменты. GitHub предоставляет инструмент уведомлений на базе CodeQL, нашего механизма семантического анализа кода, который позволяет запрашивать кодовую базу, как если бы это были данные. Наши штатные эксперты по безопасности разработали большой набор запросов для обнаружения уязвимостей безопасности во множестве популярных языков и фреймворков. Code Scanning Autofix, основанный на этой возможности обнаружения, делает ещё один шаг вперёд в обеспечении безопасности, предлагая сгенерированные искусственным интеллектом исправления для уведомлений. В первой итерации Autofix включён для уведомлений CodeQL, обнаруженных в пулл-реквесте, начиная с уведомлений JavaScript и TypeScript. Он объясняет проблему и стратегию её устранения на естественном языке, отображает предлагаемое исправление непосредственно на странице пулл-реквеста и позволяет разработчику закоммитить, отклонить или отредактировать предложенное исправление.
Основная идея Autofix проста: когда инструмент анализа кода, такой как CodeQL, обнаруживает проблему, мы отправляем затронутый код и описание проблемы большой языковой модели (LLM) и просим её предложить правки, которые исправят проблему без изменения функциональности кода. В следующих разделах мы рассмотрим детали и тонкости построения промпта к LLM, обработки ответа модели и оценки качества фичи.
Autofix промпт
В основе нашей технологии лежит запрос к LLM, выраженный в промпте. Статический анализ CodeQL обнаруживает уязвимость и генерирует уведомление, в котором указывается проблемное место в коде, а также любые другие важные места. Например, для такой уязвимости, как SQL-инъекция, уведомление отмечает место, где испорченные данные используются для построения запроса к базе данных, а также включает один или несколько путей потока, показывающих, как недоверенные данные могут попасть в это место без очистки. Мы извлекаем информацию из уведомления, чтобы построить LLM-промпт, который состоит из:
Общей информации об этом типе уязвимости, обычно включающей общий пример уязвимости и способы её устранения, извлечённой из CodeQL query help.
Местоположение исходного кода и содержание уведомления.
Релевантные фрагменты кода из мест по всему пути потока и любые места кода, на которые есть ссылки в уведомлении.
Уточнение ожидаемого ответа.
Затем мы просим модель показать, как отредактировать код, чтобы устранить уязвимость.
Мы описываем строгий формат вывода, чтобы обеспечить возможность автоматизированной обработки. Модель выдаёт Markdown, состоящий из следующих разделов:
Подробные инструкции на естественном языке по устранению уязвимости.
Полная спецификация необходимых правок кода, соответствующая формату, заданному в промпте.
Список зависимостей, которые должны быть добавлены в проект, если это применимо. Это необходимо в случае, если исправление использует стороннюю библиотеку очистки, от которой проект ещё не зависит.
Вместе с уведомлением о сканировании кода мы выводим на экран объяснение на естественном языке, а затем — diff-патч, созданный на основе правок кода и добавленных зависимостей. Пользователи могут просмотреть предложенное исправление, при необходимости отредактировать его и применить в качестве коммита в своём пулл-реквесте.
Пред- и постобработка
Если бы наша цель заключалась в создании красивой демонстрации, этой простой установки было бы достаточно. Однако для поддержания реальной сложности и преодоления ограничений LLM требуется сочетание тщательной разработки промптов и эвристики постобработки. Полное описание нашего подхода выходит за рамки этой статьи, но ниже мы опишем некоторые важные аспекты.
Выбор кода для показа модели
Уведомления CodeQL содержат информацию о местоположении уведомления, а иногда и о шагах на пути потока данных от источника к приёмнику. Иногда в сообщении уведомления содержатся ссылки на дополнительные места исходного кода. Любое из этих мест может потребовать редактирования для устранения уязвимости. Другие части кодовой базы, такие как набор тестов, также могут нуждаться в правках, но мы сосредоточимся на наиболее вероятных кандидатах из-за ограничений на длину промпта.
Для каждого из этих мест кода мы используем набор эвристик, чтобы выбрать область, которая обеспечивает необходимый контекст и при этом минимизирует количество строк кода, исключая менее значимые части, если это необходимо для достижения нужной длины. Область разработана таким образом, чтобы включать импорты и определения в верхней части файла — поскольку их часто приходится дополнять в предложении об изменениях. Когда несколько мест из уведомления CodeQL находятся в одном файле, мы структурируем комбинированный фрагмент кода, который даёт необходимый контекст для всех этих мест.
В результате получается набор из одного или нескольких фрагментов кода, потенциально из нескольких файлов исходного кода. Они показывают модели те части проекта, в которых скорее всего потребуются правки, с добавлением номеров строк, чтобы можно было ссылаться на конкретные строки как в промпте, так и в ответе модели. Чтобы предотвратить ошибки, мы явно ограничиваем модель вносить правки только в код, включённый в промпт.
Добавление зависимостей
Некоторые исправления требуют добавления новой зависимости, например библиотеки санации данных. Для этого нужно найти конфигурационный файл (или файлы) со списком зависимостей проекта, определить, включены ли уже нужные пакеты, и, если нет, сделать необходимые добавления. Можно было бы использовать LLM для всех этих шагов, но это потребовало бы показать LLM список файлов в кодовой базе, а также содержимое соответствующих файлов. Это увеличит как количество вызовов модели, так и количество токенов промпта. Вместо этого мы просто просим модель перечислить внешние зависимости, используемые в её исправлении. Мы реализуем эвристику, специфичную для языка, чтобы найти соответствующий конфигурационный файл, проанализировать его, чтобы определить, существуют ли уже нужные зависимости, и если нет — добавить нужные правки в создаваемый diff-патч.
Определение формата для редактирования кода
Нам нужен компактный формат, в котором модель сможет указывать правки кода. Наиболее очевидным вариантом было бы попросить модель напрямую вывести стандартный diff-патч. К сожалению, эксперименты показали, что такой подход усугубляет известные трудности модели с арифметикой — что часто приводит к неправильным вычислениям номеров строк без достаточного контекста кода для внесения эвристических исправлений. Мы экспериментировали с несколькими альтернативами, включая определение фиксированного набора команд редактирования строк, которые может использовать модель. Подход, который дал наилучшие результаты на практике, заключается в том, чтобы позволить модели предоставлять блоки кода «до» и «после», показывая требующие изменений фрагменты (включая некоторые окружающие контекстные строки), и правки, которые должны быть сделаны.
Преодоление ошибок модели
Мы используем различные эвристики постобработки для обнаружения и исправления небольших ошибок в выводе модели. Например, блоки кода «до» могут не полностью совпадать с исходным кодом, а номера строк могут быть немного смещены. Мы реализуем нечёткий поиск для соответствия оригинальному коду, преодолевая и исправляя ошибки в отступах, точках с запятой, комментариях к коду и так далее. Используем парсер для проверки синтаксических ошибок в отредактированном коде. Мы также реализуем семантические проверки, такие как проверка разрешения имен и проверка типов. Если мы обнаруживаем ошибки, которые не можем исправить эвристически, то помечаем предложенную правку как (частично) неправильную. В случаях, когда модель предлагает добавить в проект новые зависимости, мы проверяем, существуют ли эти пакеты в реестре пакетов экосистемы, и проверяем их на наличие известных уязвимостей безопасности или вредоносных пакетов.
Оценка и итерации
Чтобы итеративно улучшать промпты и эвристики и в то же время минимизировать затраты LLM на вычисления, нам необходимо оценивать предложения по исправлениям в требуемом масштабе. При переходе от качества Autofix демо к продакшен качеству, мы опирались на обширные автоматизированные средства тестирования, позволяющие быстро оценивать и итерировать.
Первым компонентом средств тестирования является пайплайн сбора данных. Он обрабатывает репозитории с открытым исходным кодом с уведомлениями о сканировании кода, собирая уведомления, которые имеют тестовое покрытие для места уведомления. Для JavaScript / TypeScript, первых поддерживаемых языков, мы собрали более 1 400 уведомлений с тестовым покрытием из 63 запросов CodeQL.
Второй компонент средств тестирования — workflow GitHub Actions, который запускает Autofix для каждого уведомления в наборе оценок. После коммита сгенерированного исправления в форк, workflow запускает CodeQL и тестовый набор репозитория для оценки валидности исправления. В частности, исправление считается успешным только в том случае, если оно:
убирает уведомления CodeQL;
не вводит новых уведомлений CodeQL;
не приводит к синтаксическим ошибкам;
не изменяет результатов ни одного из тестов репозитория.
По мере работы над промптом, форматом редактирования кода и различными эвристиками постобработки мы использовали эти средства тестирования, чтобы убедиться, что наши изменения повышают процент успеха. Автоматические оценки мы совмещали с периодическим ручным анализом, чтобы сосредоточить усилия на наиболее распространённых проблемах, а также для подтверждения точности автоматизированного фреймворка. Такой строгий подход к разработке, основанной на данных, позволил нам втрое увеличить коэффициент успешности и одновременно снизить требования к вычислениям LLM в шесть раз.
Архитектура, инфраструктура и пользовательский опыт
Генерирование полезных исправлений — это первый шаг, но их внедрение в работу наших пользователей требует дальнейших изменений на уровне как фронтенда, так и бэкенда. Стремясь к простоте, мы создали Autofix на основе существующей функциональности, где это возможно. Наряду с уведомлением о сканировании кода пользователи могут увидеть предлагаемое исправление, которое может включать в себя изменения в нескольких файлах, в том числе за пределами изменений пул-реквеста. Также отображается объяснение исправления на естественном языке. Пользователи могут закоммитить предложенные исправления непосредственно в пул-реквест или отредактировать их в локальной IDE или в кодовом пространстве GitHub.
Бэкэнд также построен на основе существующей инфраструктуры сканирования кода, что делает его удобным для пользователей. Пользователям не нужно вносить никаких изменений в свои рабочие процессы сканирования кода, чтобы увидеть предложения по исправлению поддерживаемых CodeQL-запросов.
Пользователь открывает пулл-реквест или пушит коммит. Сканирование кода выполняется как обычно, в рамках Actions workflow или workflow в сторонней CI-системе, результаты загружаются в формате SARIF в API для сканирования кода. Бэкенд-служба сканирования кода проверяет, относятся ли результаты к поддерживаемому языку. Если да, то запускается генератор исправлений в виде CLI-инструмента. Генератор исправлений использует данные уведомлений SARIF, дополненные соответствующими фрагментами исходного кода из репозитория, для создания запроса для LLM. Он вызывает LLM через аутентифицированный вызов API к внутреннему развёрнутому API, запускающему LLM в Azure. Ответ LLM проходит через систему фильтрации, которая помогает предотвратить определённые классы опасных ответов. Затем генератор исправлений обрабатывает ответ LLM для создания предложения об исправлении. Code Scanning бэкенд сохраняет полученное предложение, делая его доступным для отображения рядом с уведомлением в пулл-реквесте. Предложения кэшируются для повторного использования, где это возможно, что снижает требования к вычислениям LLM.
Как и в случае со всеми продуктами GitHub, мы следовали стандартным и внутренним процедурам безопасности, а также подвергли нашу архитектуру тщательному анализу на предмет безопасности и конфиденциальности, чтобы обезопасить пользователей. Мы также приняли меры предосторожности против специфических рисков, связанных с искусственным интеллектом — таких как промпт-инъекции. Хотя полная безопасность программного обеспечения никогда не гарантирована, наша Red Team проводила стресс-тестирование фильтров реакции модели и других механизмов безопасности, оценивая риски, связанные с безопасностью, токсичным контентом и предвзятостью модели.
Телеметрия и мониторинг
Прежде чем запустить Autofix, мы хотели убедиться, что сможем отслеживать его работу и измерять его влияние в реальных условиях. Мы не собираем промпт или ответы моделей, поскольку они могут содержать приватный код пользователя. Вместо этого мы собираем анонимную, агрегированную телеметрию о взаимодействии пользователей с предложенными исправлениями — например:
процент уведомлений, для которых было сгенерировано предложение об исправлении,
процент предложений, которые были зафиксированы в ветке как есть,
процент предложений, которые были применены через GitHub CLI или Codespace,
процент предложений, которые были отклонены,
и процент исправлений для уведомлений с предложениями по сравнению с уведомлениями без предложений.
По мере привлечения большего числа пользователей в бета-программу мы будем изучать эту телеметрию, чтобы понять, насколько полезны наши предложения.
Кроме того, мы отслеживаем работу сервиса на предмет ошибок, таких как перегрузка API модели Azure или срабатывание фильтров, блокирующих токсичный контент. Прежде чем расширить Autofix до неограниченной публичной бета-версии и в конечном итоге до общей доступности, мы хотим обеспечить стабильную работу сервиса.
Что дальше?
По мере того как мы раскатываем бета-версию Code Scanning Autofix для всё большего числа пользователей, мы собираем отзывы, исправляем ошибки и отслеживаем показатели, чтобы убедиться, что наши предложения действительно полезны для устранения уязвимостей в системе безопасности. Параллельно мы расширяем возможности Autofix на большее количество языков и сценариев использования, а также улучшаем пользовательский опыт. Если вы хотите присоединиться к публичной бета-версии, регистрируйтесь по ссылке. Следите за обновлениями!
Приглашаем начинающих fullstack-разработчиков на открытые уроки:
4 июня: Преимущества семантической верстки. На этом уроке рассмотрим семантические теги и то, как они влияют на сайт; изучим основные и применим их на практике. Записаться
18 июня: Объектно-ориентированный JavaScript и функции конструкторы. Изучим, как создавать объекты при помощи функций конструкторов, a также как наследуются свойства в объектах. Записаться
iagan
Возможно ли проверить код на уязвимости оффлайн, без его передачи третьей стороне?