Разбираем механику открытого AI-агента GitHub Security Lab TaskflowСпособ автоматизировать, упаковать и делиться AI-промптами и рабочими процессами, которые исследователи безопасности считают эффективными, собираемый из шагов, каждый из которых задаётся отдельным YAML-файлом. Agent: как из YAML-описаний собирается последовательность шагов, как агент определяет точки входаместа в коде, через которые могут проходить контролируемые атакующим данные в Android-приложении и почему находки всё равно приходится проверять вручную. Нетривиальность в том, что агент ищет не только шаблонные классы ошибок, но и логические уязвимости, а слабое место у него ровно одно — оценка серьёзности.
Что такое taskflow и как он устроен
Taskflow — это способ автоматизировать, упаковать и делиться AI-промптами и рабочими процессами, которые исследователи безопасности считают эффективными. Он собирается из шагов, каждый из которых задаётся отдельным YAML-файлом.
Работа между шагами распределяется так. Один шаг берёт точки входа и разделяет их на мобильные и немобильные, чтобы ИИ работал с репозиториями разных типов приложений и понимал правильную поверхность атаки. Другой шаг задаёт список популярных классов уязвимостей, и LLM просят рассмотреть их в контексте каждой точки входа и компонента.
YAML-описание превращается в последовательность действий агента запуском скрипта:
./scripts/audit/run_mobile.sh myorg/myrepoВходной параметр — репозиторий в формате myorg/myrepo. Результаты складываются в SQLite-таблицу audit_results, где строки с отметкой в столбце has_vulnerability считаются содержащими уязвимость.
Как запустить taskflows на своём проекте
Для запуска нужна лицензия GitHub Copilot, а промпты используют premium model requestsзапросы к более мощной модели, которые расходуют больше токенов, чем обычные. Выполнение порождает множество вызовов инструментов и легко израсходует большое количество токенов. Порядок шагов такой: перейти в репозиторий seclab-taskflows и запустить codespaceоблачную среду разработки, которая разворачивается прямо в браузере, подождать несколько минут инициализации, затем в терминале выполнить ./scripts/audit/run_mobile.sh myorg/myrepo. На среднем по размеру репозитории это может занять час или два. По завершении открывается просмотрщик SQLite с результатами.
Роль CodeQL и code scanning
CodeQLдвижок анализа кода, который представляет кодовую базу как базу данных и выполняет по ней запросы для поиска уязвимостей и ошибок представляет кодовую базу как базу данных и выполняет по ней запросы, чтобы находить уязвимости и ошибки; результаты показываются как оповещения code scanning в GitHub.
В пайплайне с ИИ-агентом CodeQL не участвует. Агент на базе LLM ищет уязвимости, разбивая исследование на шаги, и находит логические уязвимости с критическим влиянием, а не только общие классы ошибок. Результаты не объединяются автоматически: находки агента попадают в таблицу audit_results, и строки с отметкой в has_vulnerability далее требуют ручной проверки. Причина в том, что LLM хорошо находит уязвимости, но плохо оценивает серьёзность, поэтому каждую находку должен проверять исследователь безопасности, знающий мобильные приложения.
Механика поиска: как агент находит точки входа
Шаг gather_mobile_entry_point_info.yaml берёт точки входа и разделяет их на мобильные и немобильные. Это позволяет ИИ работать с репозиториями разных типов приложений — мобильных, веб-серверов, десктопных — и при этом понимать правильную поверхность атаки.
Дальше в classify_application_local.yaml задаётся список популярных классов уязвимостей, которые LLM рассматривает в контексте каждой точки входа и компонента. Для конкретных типов точек входа список уточняется: если на предыдущем шаге taskflow определил точку входа на основе intent, для неё задаётся список типичных intent-уязвимостей, таких как confused deputyатака, при которой приложение с бо́льшими привилегиями выполняет действие по запросу менее привилегированного или insecure broadcastшироковещательные сообщения, которые принимает любое приложение и через которые можно передать вредоносные данные.
Как агент фильтрует шум
Отдельного порога отсечения нет — фильтрация устроена иначе. Чтобы не пропустить очевидные уязвимости, используются строгий промпт и повторные запуски, а широкий промпт позволяет проявить креативность. При этом агент может возвращать низкоприоритетные и труднореализуемые проблемы, поэтому каждую находку должен проверять исследователь.
Точность оценки серьёзности страдает из-за смягчающих факторов, которые LLM трудно увидеть без явного указания создать proof of conceptрабочий пример эксплуатации, демонстрирующий, что уязвимость действительно срабатывает. Это требует нескольких запусков — не только для поиска уязвимостей, но и для создания proof of concept, что заставляет LLM пытаться проэксплуатировать уязвимость.
Кейс 1: OsmAnd — импорт настроек и утечка тайлов
Импорт произвольных настроек
Функция handleOsmAndSettingsImport принимает Uri, имя файла и Bundle extras; сначала из имени файла удаляется ZIP_EXT. Затем проверяется, содержит ли extras хотя бы один из ключей SETTINGS_VERSION_KEY или SETTINGS_LATEST_CHANGES_KEY. Если да, из extras читаются version (int, по умолчанию -1), latestChanges (String), replace (boolean), silentImport (boolean) и exportTypeKeys (ArrayList<String>), после чего вызывается перегрузка handleOsmAndSettingsImport с этими значениями. Если условие не выполнено, вызывается та же перегрузка с безопасными значениями: null, false, false, null, -1.
Два параметра определяют последствия: silentImport позволяет импортировать без уведомления, replace — заменять, а не только добавлять настройки. Поскольку MapActivity экспортирована, любое приложение может отправить intent с произвольными extras, и Android не предоставляет механизма ограничить, какие extras может задать внешний вызывающий. Это и позволяет импортировать произвольные настройки.
Подмена tile-URL
URL тайла формируется подстановкой строковых zoom, x и y в шаблон:
MessageFormat.format(urlTemplate, zoom + "", x + "", y + "")По умолчанию OsmAnd использует локальные тайлы, но их можно перезаписать, задав URL вида f"{ATTACKER_DOMAIN}/tiles/{{0}}/{{1}}/{{2}}.png", где {0}, {1}, {2} — места для zoom, x, y. Замена локальных тайлов на удалённые с attacker-домена происходит после импорта произвольных настроек. В результате сервер атакующего получает точные координаты x, y каждого загруженного тайла, а серверная часть отдаёт соответствующее изображение тайла из OpenStreetMaps, так что пользователь не замечает изменения настроек.
Что утекает
Утекают координаты тайла x и y, которые подставляются в шаблон URL вместо плейсхолдеров. В логе вида [TILE #1] 14:23:07 z=15 x=9649 y=12320 показаны z=15, x=9649, y=12320, а также вычисленный центр center: 40.70979, -73.98743 и ссылка на OpenStreetMap с этими же координатами. То есть по x, y и z определяется географическая точка — центр тайла — в виде широты и долготы.
Утечка маршрутов
Та же уязвимость позволяет получить origin и destination каждого маршрута, который пользователь строит в OsmAnd, и отправить их на сервер атакующего без заметных для пользователя изменений. В логе маршрута формируется путь вида /osrm/car/-122.084,37.4219983;-122.32450103759766,37.99944305419922, где vehicle=car и waypoints=2. В этот же лог попадают координаты ORIGIN 37.421998, -122.084000 и DESTINATION 37.999443, -122.324501, а также ссылки на OpenStreetMap для каждой точки.
Кейс 2: Wikipedia — захват аккаунта через deeplink
Перенаправление на произвольный сайт
Функция handleIntent принимает Intent и срабатывает только при одновременном выполнении двух условий: действие равно Intent.ACTION_VIEW и intent.data не равно null. Если условия выполнены, берётся URI из intent.data и проверяется, заканчивается ли его authorityчасть URI, задающая хост и порт на WikiSite.BASE_DOMAIN; при совпадении URI преобразуется заменой префикса "wikipedia://" на WikiSite.DEFAULT_SCHEME + "://", после чего запускается PageActivity с действием ACTION_VIEW и этим URI.
Примитив перенаправления на произвольный сайт возникает из-за логической ошибки в парсере hostname, позволяющей загружать не-Wikipedia URL через deeplinkссылка, открывающая приложение напрямую, минуя браузер wikipedia://. Пользователь думает, что находится на странице Wikipedia, а на самом деле — на подконтрольной атакующему странице, где может исполняться произвольный JavaScript в WebView приложения.
Утечка cookies
Проверка domain.endsWith(domainSpec) сравнивает строку домена с ожидаемым суффиксом и, если домен заканчивается на этот суффикс, добавляет cookies для этого domainSpec в список:
if (domain.endsWith(domainSpec)) { buildCookieList(cookieList, cookiesForDomainSpec, null) }Так как проверка не требует точного совпадения, домен вида evil-wikipedia.org тоже заканчивается на wikipedia.org, поэтому приложение отдаёт cookies, предназначенные для wikipedia.org, стороннему домену.
В сочетании с deeplink цепочка работает так: жертва открывает вредоносную страницу с deeplink и нажимает на него; приложение Wikipedia автоматически открывается и загружает контролируемую атакующим страницу, заканчивающуюся на wikipedia.org, например evil-wikipedia.org. Жертва думает, что это страница Wikipedia, а приложение автоматически отправляет её cookies. В результате атакующий получает имя пользователя жертвы, долгоживущий токен и session tokenтокен, подтверждающий активную сессию пользователя, действительный во всех проектах Wikimedia.
Что именно утекает и что видит пользователь
Утекают все cookies страницы Wikipedia, которые являются долгоживущими, для домена wikipedia.org. Атакующий получает доступ к имени пользователя жертвы, долгоживущему токену и сессионному токену, действительному во всех проектах Wikimedia (все Википедии, Commons, Wikidata, Meta и т.д.).
Пользователь видит, как приложение Wikipedia автоматически открывается и загружает контролируемую атакующим страницу, заканчивающуюся на wikipedia.org, например evil-wikipedia.org; жертва думает, что это страница Wikipedia, и приложение автоматически отправляет cookies пользователя.
Ограничения LLM-агента: что он умеет и где ошибается
LLM хорошо находит уязвимости, включая логические с критическим влиянием, но плохо оценивает их серьёзность. Он возвращает проблемы, требующие почти невоспроизводимых состояний, и часто сообщает о низкоопасных уязвимостях даже при явной просьбе этого не делать, поэтому каждую находку должен проверять исследователь безопасности.
Оценка серьёзности часто неверна, потому что реальное влияние меняется из-за смягчающих факторов, которые LLM трудно увидеть без явного запроса на создание proof of concept. Пример: path traversalатака, при которой злоумышленник подставляет в путь к файлу переходы вида «../», чтобы выйти за пределы разрешённой папки в Android-приложении, где путь ограничен внешним хранилищем, имеет низкую относительную серьёзность. Ещё пример: если приложение использует данные и из внутреннего, и из внешнего хранилища, внутренние данные часто имеют приоритет, и LLM может ошибочно решить, что записанные через path traversal внешние данные изменят данные приложения, хотя внутреннее хранилище перезаписывает их — тогда уязвимости нет вовсе.
Такие сложные поведения ведут к ложным срабатываниям, и пока единственный способ исправить это — дать LLM отладчик для запуска proof of concept и исходного кода либо попросить исследователя специально искать такие проблемы. На приоритизацию это влияет так: из-за ложных срабатываний и неверной оценки серьёзности находки нужно проверять вручную, а на уязвимости со слабым влиянием модель может тратить дополнительное время.
Практика: как воспроизвести и что делать мейнтейнерам
Чтобы запустить taskflows на своём репозитории, мейнтейнеру нужно перейти в репозиторий seclab-taskflows и запустить codespace, подождать несколько минут инициализации, затем в терминале выполнить ./scripts/audit/run_mobile.sh myorg/myrepo. Требуется лицензия GitHub Copilot, а промпты используют premium-запросы, что может израсходовать много токенов. По завершении откроется SQLite-просмотрщик с результатами; в таблице audit_results нужно искать строки с галочкой в столбце has_vulnerability.
Отдельно стоит учитывать ограничения CodeQL по языкам. Он поддерживает C/C++, C#, Go, Java/Kotlin, JavaScript/TypeScript, Python, Ruby, Rust, Swift и GitHub Actions workflows. Чтобы указать, какой язык анализировать, в конфигурации используются идентификаторы: java-kotlinидентификатор языка CodeQL для анализа кода на Java, Kotlin или обоих сразу — для Java и Kotlin, javascript-typescriptидентификатор языка CodeQL для анализа кода на JavaScript, TypeScript или обоих сразу — для JavaScript и TypeScript. CodeQL не поддерживает языки, не перечисленные выше, включая PHP и Scala; попытка использовать неподдерживаемые языки может привести к отсутствию алертов и неполному анализу. Для кастомных или нишевых фреймворков можно использовать расширение CodeQL для Visual Studio Code, чтобы создавать модели зависимостей и расширять анализ.
Что из этого следует на практике
- Находки агента — это вход в ручную проверку, а не готовый список. LLM хорошо находит уязвимости, но плохо оценивает серьёзность, поэтому каждую находку должен проверять исследователь безопасности, знающий мобильные приложения.
- Оценку серьёзности нужно выносить в отдельный шаг с proof of concept. Смягчающие факторы LLM трудно увидеть без явного запроса «create a proof of concept», а это требует нескольких запусков и заставляет модель пытаться проэксплуатировать уязвимость.
- Ложные срабатывания предсказуемы там, где есть приоритет источников данных. Если внутреннее хранилище перезаписывает подконтрольные атакующему внешние данные, уязвимости нет вовсе — такие случаи ведут к ложным срабатываниям.
- Разделение точек входа на мобильные и немобильные — то, что делает агент применимым к репозиториям разных типов. Один и тот же пайплайн работает и на мобильном приложении, и на веб-сервере, и на десктопном приложении, сохраняя понимание правильной поверхности атаки.
- Запуск стоит токенов и времени. Нужна лицензия GitHub Copilot, промпты используют premium model requests, выполнение порождает множество вызовов инструментов, а на среднем репозитории уходит час или два.
- Строгий и широкий промпты решают разные задачи. Строгий вместе с повторными запусками не даёт пропустить очевидные уязвимости, широкий — позволяет проявить креативность; оба нужны.
- Ограничения CodeQL по языкам надо проверять до запуска. Для неподдерживаемых языков алерты могут не генерироваться вовсе, а анализ будет неполным.