Шесть критических CVE в SQLite выдумала LLM. Одной дали 10.0, а функции из отчёта не существует
В отчёте о критической уязвимости в SQLite указаны строки 3555 и 3575 файла src/json.c. Проблема в том, что в заявленной версии 3.41.0 этот файл длиной 2706 строк. Строк 3555 там нет. И самой функции, в которой якобы сидит use-after-free, в этой версии тоже нет.
Такой отчёт получил в NVD оценку 7.5 High. Его сосед по репозиторию — 9.8 Critical. А ещё одному Red Hat поначалу выдал ровные 10.0.
TL;DR: Свежесозданный GitHub-репозиторий вывалил пачку advisory на SQLite; NVD пометила их как критические, CISA согласилась. JFrog проверила шесть штук — все выдуманы: несуществующие функции, номера строк за пределами файла, «патчи», которых нет в диффе, PoC, не проходящие даже парсер. Остальные 49 CVE в том же репозитории (libraw, ESP32-audioI2S) тоже фейк, кроме одного реального бага с непроверенными метаданными. MITRE отклонила всю пачку.
Что именно нафантазировала модель
Шесть advisory, шесть разных способов ошибиться — и ни одного, который выдержал бы пять минут проверки по исходникам.
- CVE-2026-51302, 9.8 Critical. Заявлен UAF:
sqlite3ReleaseTempReg()оставляет висячий указатель, который потом разыменовываетexprComputeOperands(). ФункцииexprComputeOperands()в SQLite 3.41 не существует, она появилась в середине 2025 года. Вдобавокsqlite3ReleaseTempReg()вообще не освобождает память: она складывает индексы регистров в массив для переиспользования, так что UAF там невозможен архитектурно. Именно этой CVE Red Hat сначала поставил 10.0, потом снизил до 7.6. - CVE-2026-51303, 9.8 Critical. Заявлено, что
ExprListDelete()не чистит обратные ссылки в родительских структурах, и что это починили в 3.51.3. Обратных ссылок в структурахExpr,SelectиWindowнет вообще. А дифф между 3.51.2 и 3.51.3 не содержит ни одного изменения вsrc/expr.c— «патч» выдуман целиком. PoC при этом невалидный SQL и падает на парсере, до исполняемой логики не доходя. - CVE-2026-51300, 9.1 Critical. Ссылается на строки 1012 и 1026 в
expr.c. По этим адресам лежат комментарий и вызов выделения памяти, ни то, ни другое к удалению указателей отношения не имеет. - CVE-2026-51297, 8.8 High. Обвиняет
jsonBlobEdit(), которой в 3.41.0 нет: она приехала позже вместе с реализацией JSONB. - CVE-2026-51296, 7.5 High. Тот самый случай со строками 3555 и 3575 в файле из 2706 строк.
- CVE-2026-51304, 7.5 High. Утверждает, что
sqlite3ExprListDelete(pOrderBy)освобождает список, который потом читают. Такой односоставной подписи не существует, реальная требует ещё и указатель на контекст БД. А SQLite прямо после удаления обнуляет указатель:pPrior->pOrderBy = 0;.
Методика проверки у JFrog простая и воспроизводимая: клонировали официальный репозиторий, выкатили теги version-3.41.0, 3.51.2 и 3.51.3, собрали релизы в изолированных Docker-контейнерах, скормили PoC из каждого advisory собранным бинарникам под AddressSanitizer, отдельно сверили CPE и метаданные в NVD и GHSA. Ни один PoC не вызвал ни падения, ни утечки. Плюс отдельный признак, который к делу не подшить, но который совпал: при прогоне через GPTZero весь набор advisory помечается как сгенерированный.
Как выдумка получает статус Critical в NVD
Потому что в цепочке от заявки до записи с оценкой 9.8 не осталось ни одного места, где кто-то обязан воспроизвести баг.
Публичная форма подачи CVE у MITRE не требует верификации личности: описание уязвимости и предложенный CVSS может отправить практически кто угодно. Раньше страховкой служил NIST: эксперты NVD вручную разбирали и обогащали входящие записи. Эта страховка сломалась в феврале 2024 года, когда поток отчётов вырос настолько, что глубокий анализ пришлось остановить. CISA и другие Authorized Data Publishers попытались закрыть дыру своим обогащением, но конвейер в итоге фрагментировался и оброс бэклогом.
Дальше работает автоматика. Advisory заходит, получает предложенный автором CVSS, ADP его подтверждает, запись расходится по фидам. Формально всё корректно: каждый участник сделал свою часть, воспроизведение баги не входило в чью-либо часть ни у кого.
Показательно, что SQLite здесь оказался идеальной мишенью. Проект официально не отслеживает CVE — разработчики починили бы реальный баг за часы, но в конвейер уязвимостей они не играют и часто узнают о CVE спустя месяцы. То есть вендора, который громко скажет «этой функции у нас нет», в цепочке просто не предусмотрено. Опровержение пришло от сторонней компании через несколько дней, когда записи уже разошлись по базам.
Мы писали про AI-слоп в баг-баунти curl, где Стенберг ввёл мгновенный бан для авторов сгенерированных отчётов. Разница в том, что у curl слоп упирался в живого мейнтейнера и умирал на входе. Здесь он миновал людей полностью и вышел с официальной оценкой критичности.
Чем это опаснее просто потраченного времени
Тем, что фейковая CVE ломает не человека, а автоматику, которая на неё реагирует — и особенно ту автоматику, которую сейчас массово строят на LLM.
Сценарий из отчёта JFrog, и он не гипотетический: в организациях, где критичное приоритизируется автоматически, запись 9.8 сама открывает тикет. В организациях, где триаж и починку отдали AI-агенту, всё интереснее. Агент получает CVE, идёт искать уязвимую функцию, не находит её (потому что её не существует), но задача-то поставлена. Дальше он либо генерирует патч к коду, которого нет, либо решает, что похожая функция и есть искомая, и правит её.
Второй вариант — это внесение изменений в рабочий код ради несуществующей уязвимости. Худший возможный исход процесса, который затевался ради безопасности.
Отдельная деталь для калибровки доверия. В комментариях к заметке LWN читатель обратил внимание на неудобное совпадение: JFrog публикует разбор про фейковые CVE в те же дни, когда обсуждается настоящая дыра в её собственном Artifactory — та, через которую модели OpenAI пробрались в инфраструктуру Hugging Face. Мотивы читателя проверить нельзя, а вот техническая часть разбора проверяется по исходникам SQLite за десять минут и подтверждается. Как раз это и есть правильная реакция на любой security-отчёт, включая отчёт о фейковых отчётах: смотри на воспроизводимость, а не на репутацию.
Подводные камни
- Пачка отклонена, но фиды помнят. MITRE отклонила все advisory, однако записи успели разойтись по зеркалам и агрегаторам, и часть из них до сих пор отдаёт старые данные с прежним CVSS. Твой сканер может ругаться на SQLite ещё недели.
- Оценки менялись на ходу. У одной и той же CVE было 10.0 у Red Hat, потом 7.6, и 0 в некоторых зеркалах. Если ты снимаешь метрики риска по CVSS из фида, ты снимаешь шум.
- В той же пачке был один реальный баг. Из 49 остальных CVE (libraw и ESP32-audioI2S) одна описывала настоящую проблему, но с непроверенными метаданными. Правило «репозиторий помечен как слоп, значит всё содержимое можно игнорировать» здесь не работает и приводит к пропуску настоящей уязвимости.
- GPTZero — не доказательство. Детекторы AI-текста дают ложные срабатывания, и в отчёте JFrog это лишь дополнительный признак рядом с шестью техническими опровержениями. Строить вывод на одном детекторе нельзя, особенно если решение — забанить автора.
- Отсутствие CVE на сайте вендора тоже не приговор. Для SQLite это сильный сигнал, потому что у проекта есть страница. Для тысяч мелких библиотек, у которых advisory-страницы нет вообще, признак не работает.
Альтернативы: как проверять CVE, не тратя день
- Сверка с вендором — самый быстрый фильтр. Есть ли запись на официальной странице advisory или в security-рассылке проекта. Для SQLite, curl, OpenSSL и прочих серьёзных проектов отсутствие там равно почти нулевой вероятности.
- Наличие коммита или PR с починкой — второй фильтр, и он же убивает большинство слопа. У реальной уязвимости есть конкретный коммит. Если advisory заявляет исправление в версии X, сделай
git diffмежду X-1 и X по указанному файлу. В случае CVE-2026-51303 этого хватило бы: изменений вexpr.cтам ноль. - Проверка ссылок на код — третий фильтр, самый дешёвый из всех. Существует ли функция в заявленной версии (
git grepпо тегу), лежит ли указанная строка внутри файла (wc -l). Ловит четыре из шести разобранных случаев за минуту. - Прогон PoC под sanitizer'ом — окончательный ответ, если первые три фильтра дали неоднозначный результат. Сборка в контейнере с ASan занимает больше времени, зато выдаёт факт, а не оценку вероятности.
Вердикт
Если у тебя автоматическая приоритизация по CVSS или тикеты, открывающиеся по оценке критичности, поставь между фидом и очередью человека или хотя бы три дешёвые проверки: есть ли запись у вендора, есть ли коммит с починкой, существуют ли упомянутые функция и строка. Это минуты работы и они отсеивают ровно тот класс мусора, который теперь генерируется дешевле, чем проверяется.
А если ты собрался поручить триаж уязвимостей AI-агенту — не давай ему права коммитить патчи. Этот инцидент показывает конкретный путь, которым такой агент вносит изменения в рабочий код ради уязвимости, которой никогда не было. Читающий агент полезен, пишущий на этих данных опасен. И да, к своим сканерам стоит относиться так же: оценка 9.8 в NVD теперь означает «кто-то так написал», а не «кто-то это проверил».
Как попробовать проверить любую CVE за пять минут
- Открой запись у вендора: для SQLite это sqlite.org/cves.html, для остальных — security-страница проекта. Нет записи у серьёзного проекта — сразу подозрительно.
- Проверь существование кода в заявленной версии:
git cloneрепозитория,git checkout <тег версии>, затемgit grep -n "имяФункции". Пусто — advisory выдуман. - Проверь номер строки:
wc -l путь/к/файлу. Если в advisory строка 3555, а в файле 2706 строк, дальше можно не читать. - Проверь заявленный патч:
git diff <версия-до>..<версия-после> -- путь/к/файлу. Пустой дифф при заявленном исправлении — фабрикация. - Если сомнения остались, собери в контейнере с
-fsanitize=addressи скорми PoC. И сравни свой вывод с методикой JFrog — там весь маршрут расписан по шагам.