Бэкдор в Cursor Marketplace: доверие форков VS Code
Бэкдор в расширении Claude Code на Cursor Marketplace показал, что форки VS Code не наследуют модель доверия Microsoft. Что проверить сейчас.

Разработчик открывает Cursor во вторник утром, видит обновление расширения Claude Code и разрешает установку. Ничего не выглядит иначе. Расширение загружается, AI-ассистент работает, а к обеду на машине уже лежит ключ Ed25519 злоумышленника в ~/.ssh/authorized_keys, обратный SSH-туннель стучится на сервер в интернете, а LaunchAgent, замаскированный под системный процесс Apple, поддерживает всё это после перезагрузок. Это и есть бэкдор в Cursor Marketplace, о котором раскрыла информацию компания Phorion. Это самая наглядная демонстрация того, о чём те, кто работает в безопасности, ворчат уже много лет: форки VS Code наследуют кодовую базу Microsoft, но не наследуют её модель доверия.
Большую часть карьеры я занимался реагированием на инциденты на машинах разработчиков, и этот случай задевает за живое своей обыденностью способа доставки. Никакого фишинга. Никакого zero-day. Просто подменённый пакет расширения, версии 2.1.312 и 2.1.313, опубликованный в маркетплейсе, которому разработчики доверяют по умолчанию, потому что он выглядит как тот, которому они и так доверяют. Официальные каналы Anthropic были чистыми: кто-то получил доступ к конвейеру публикации Cursor и выложил модифицированную копию расширения, которую Anthropic для этого реестра никогда не собирала. Зона поражения — все пользователи Cursor, которые автоматически обновились в течение одиннадцатичасового окна.
Что на самом деле делал бэкдор
Имплант представлял собой immediately invoked function expression, добавленное в начало минифицированного бандла extension.js. Всё, что шло после внедрённого префикса, выглядело как обычное расширение: те же экспорты activate и deactivate, тот же UI-код, те же строки. Именно это должно тревожить, если ваша стратегия обнаружения — «сравнить бандл с апстримом»: вредоносная часть — это несколько сотен байт дополнительного JavaScript поверх легитимного файла на мегабайты. Он выполняется в момент, когда Node загружает модуль, ещё до того, как срабатывает собственный callback activate расширения. Пользователю не нужно ничего нажимать, вызывать Claude или как-либо взаимодействовать с расширением.
Полезная нагрузка — это работа из четырёх этапов, и, честно говоря, сделана она грамотно:
- Закрепление ключа. Добавляет публичный ключ Ed25519 злоумышленника с комментарием
svc-telemetryв~/.ssh/authorized_keysс правильными правами 0700/0600. - Включение SSH. Пытается убедиться, что sshd запущен: systemctl в Linux, Remote Login в macOS, OpenSSH Server через PowerShell в Windows.
- Обратный туннель. Открывает исходящее SSH-соединение к серверу, контролируемому оператором, с
-R *:0:localhost:22, так что злоумышленник может подключиться через туннель к порту 22 жертвы и войти с подложенным ключом. Собственный протокол C2 не нужен — SSH и есть C2. - Персистентность и разведка. Пользовательский LaunchAgent с именем
com.apple.sysmond.plistв macOS, запись @reboot в cron в Linux, запасной вариант через папку «Автозагрузка» в Windows. Затем разовый маячок с именем хоста, именем пользователя, email из git, публичным IP и до сорока именами переменных окружения, совпадающих с/KEY|TOKEN|SECRET|PASS|AWS|GCP/i.
Последний пункт хитро устроен, и его стоит понять. Маячок не выгружает ваш секретный ключ AWS — он выгружает сам факт, что он у вас есть. Это инвентарь целей. Оператор получает меню: какие скомпрометированные машины принадлежат облачным инженерам с живыми учётными данными в окружении — и именно к ним он вернётся первым через туннель.
Баг в macOS, который случайно спас людей
Как специалист по реагированию на инциденты, я нахожу здесь одну деталь мрачно забавной: путь включения в macOS сломан из-за классической ошибки в скрипте. Код запускает systemsetup -getremotelogin от непривилегированного пользователя и пытается включить Remote Login, только если вывод совпадает с /Off/i. Но в macOS эта команда отказывается работать без прав администратора. Она выводит в stdout «You need administrator access to run this tool... exiting!» и завершается с кодом 0.
// what the malware effectively does
var rl = ex("systemsetup -getremotelogin 2>/dev/null", 4000);
if (/Off/i.test(rl)) {
det("sudo -n systemsetup -setremotelogin on >/dev/null 2>&1");
}
В строке ошибки нет «Off», поэтому регулярка не срабатывает, и шаг включения никогда не выполняется. Скрипт воспринимает «у меня нет прав проверить, выключен ли SSH» как «SSH уже включён». Код возврата 0 от упавшей команды — один из старейших капканов в shell-скриптах, и на этот раз он сработал против автора малвари, а не против сисадмина. На Mac, где Remote Login ещё не включён, обратный туннель подключается нормально, но всё, что пробрасывается назад, упирается в закрытый порт 22. Ключи и персистентность всё равно оседают — машина скомпрометирована, — но путь интерактивного доступа мёртв с самого начала.
Не утешайтесь этим. Если у пользователя уже включён Remote Login или он включит его позже, подложенный ключ в authorized_keys просто ждёт своего часа. У пути для Windows похожий баг (он передаёт команду PowerShell через помощник, который жёстко прописывает /bin/sh), а логика cron в Linux может стереть существующий crontab пользователя при повторных запусках. Малварь, написанная людьми, содержит человеческие баги. Следующая не будет такой.
Почему форкнутые маркетплейсы не могут унаследовать модель доверия Microsoft
Это структурный момент, и именно над ним я хочу, чтобы задумались инженерные руководители. Когда вы ставите расширение из официального Visual Studio Marketplace, за ним стоит реальный конвейер: аккаунты издателей с верификацией домена, сканирование при публикации, процессы снятия с публикации и компания с юридическим отделом на другом конце. Он далеко не идеален — вредоносные расширения через него проскакивали, — но это система.
Форки VS Code вроде Cursor не могут использовать этот маркетплейс по лицензионным причинам, поэтому они подключаются к альтернативным реестрам — Open VSX или собственной инфраструктуре. Интерфейс редактора до последнего байта знаком. На странице расширения то же название, та же иконка, правдоподобная строка издателя. Но каждый механизм, который делал оригинальный маркетплейс надёжным, либо переписан заново, либо отсутствует. Кто проверял издателя? Кто сканировал бандл? Собран ли артефакт из заявленного исходного репозитория, и можно ли это проверить? В этом инциденте подменённые версии, судя по всему, вообще никогда не существовали в реестре Microsoft — они жили только на стороне форка. Доверие было чисто визуальным.
Мы видели то же кино в мире пакетных реестров. Компрометация LiteLLM на PyPI сработала, потому что слабое место — это доступ к публикации, а не код. Расширения хуже пакетов в одном важном отношении: здесь нет культуры lockfile, нет воспроизводимости вроде npm ci, нет фиксации хешей. Редактор тихо обновляется сам, а, как обнаружил один комментатор на Hacker News, задержка автообновления VS Code даже не действует для «доверенных издателей» — списка, который нельзя настроить. Ваш набор расширений — это живое, незафиксированное дерево зависимостей, которое работает с вашими полными привилегиями пользователя и обновляется само, пока вы спите.

ИИ-инструменты для программирования — это эскалация привилегий, которую вы выбрали сами
Отбросьте драму вокруг маркетплейса и посмотрите, какой на самом деле была цель. AI-расширение для кодинга — это не цветовая тема. Оно работает с правами IDE, а это ваши права, и его главная задача — читать ваше дерево исходников, хранить ваши API-ключи и всё чаще выполнять shell-команды от вашего имени. Когда мы писали о том, как агентные CLI для кодинга управляют правами, повторяющейся темой было то, что полезный агент и опасный агент отличаются политикой, а не возможностями. Расширение, которое может запускать child_process, отделено от запуска чего угодно одной плохой публикацией.
Один комментатор сформулировал прямо: все, кто пользуется этими инструментами, фактически ежедневно запускают curl-pipe-bash, а мы десять лет объясняли джунам, что так делать нельзя. Это преувеличение, но лишь слегка. Разница между curl | bash и автообновляемым агентным расширением в том, что у расширения приятнее интерфейс. Я уже писал о том, что CLI-инструменты не должны по умолчанию ставить фоновые демоны; этот инцидент — то, что случается, когда демон установлен кем-то другим, и вы вообще не давали согласия. Отсоединённый процесс ssh, переживающий выход из IDE, — это ровно тот паттерн поведения, который я отмечаю на ревью, а здесь его доставил злоумышленник через доверенный канал обновлений.
Как проверить парк машин прямо сейчас
Если кто-то в вашей организации использует Cursor, считайте это активным инцидентом, а не новостью. Индикаторы с высокой информативностью, прямо из форензик-разбора:
# macOS / Linux: the planted key is the cleanest signal
grep -s svc-telemetry ~/.ssh/authorized_keys && echo "IMPACTED" || echo "key not found"
# Check persistence artifacts
ls ~/Library/LaunchAgents/com.apple.sysmond.plist 2>/dev/null # macOS
ls ~/.config/.smt ~/.config/.smtk 2>/dev/null # macOS + Linux
crontab -l 2>/dev/null | grep -F .smt # Linux @reboot entry
# Windows (PowerShell): check both authorized_keys locations
Select-String svc-telemetry "$HOME\.ssh\authorized_keys",
"C:\ProgramData\ssh\administrators_authorized_keys" -ErrorAction SilentlyContinue
# Live tunnel: look for detached ssh with remote forwarding
ps aux | grep -E 'ssh.*-R' | grep -v grep
Несколько заметок по поиску из опыта. Во-первых, проверяйте отпечаток ключа, а не только комментарий — имплант дедуплицирует по строке комментария svc-telemetry, и так же может поступить попытка очистки. Во-вторых, файл туннельного ключа лежит во временной директории с именем вида .stk_ и десятью hex-символами, полученными из MD5 от имени хоста и пути к домашнему каталогу; он детерминирован для каждой машины, так что точное имя файла можно вычислить и проверить. В-третьих, телеметрия EDR не покажет попытку включения в macOS для обычных пользователей, потому что из-за бага она никогда не выполняется. Отсутствие systemsetup -setremotelogin в логах не означает отсутствия компрометации. Ключ и LaunchAgent — вот объективная истина.
Если вы нашли ключ, машину нужно пройти по полной процедуре реагирования: ротировать каждое учётное данное, имя переменной окружения которого могло попасть в маячок (а это почти все — регулярка ловит AWS, GCP и всё, где в имени есть KEY или TOKEN), отозвать доступ машины и переустановить систему. Не ограничивайтесь удалением ключа и не считайте вопрос закрытым. У оператора был или предпринимался интерактивный shell-доступ, и по одному только коду импланта вы не можете перечислить, к чему он прикасался. Здесь же проявляются слепые зоны логирования аутентификации в облаке: проверьте облачные журналы аудита на предмет использования консоли или API в окне между обновлением расширения и раскрытием, сопоставляя по учётным данным затронутого пользователя.

Усиление защиты: изолируйте редактор, а не только расширение
После инцидента все спрашивают, что менять. Моя позиция, и я готов её защищать: аудитом безопасную экосистему расширений вы никогда не получите, поэтому нужно сдерживание. Общий компромисс мы разбирали в материале о сдерживании против мониторинга AI-агентов, а этот инцидент — кейс именно про сдерживание. Мониторинг здесь не сообщил бы ничего, пока сторонняя security-компания случайно не разобрала бы бандл.
- Фиксируйте версии расширений и отключайте автообновление для всего, что имеет широкие права, и применяйте то же правило к форкам. Да, вы унаследуете усталость от обновлений. Это дешевле, чем реагирование на инциденты.
- Фильтрация исходящего трафика на рабочих станциях разработчиков. Вся модель доступа здесь зависела от исходящего SSH-соединения к IP VPS. Если ваши машины разработчиков не могут открывать произвольные исходящие соединения на порт 22, этап с туннелем умирает. Почти никто этого не делает, а это полностью обезвредило бы данный имплант.
- Следите за состоянием SSH как за канарейкой. Настройте оповещения о включении Remote Login, новых ключах в authorized_keys и процессах sshd на машинах, где sshd быть не должно.
- Ведите инвентаризацию ИИ-инструментов так же, как инвентаризуете серверы. Знайте, какие расширения и агентные CLI одобрены, какие права у них есть и из какого маркетплейса они пришли. Если ответ на вопрос «из какого реестра ставятся наши установки Cursor» — это пожимание плечами, это проект на следующий квартал.
- Отдавайте предпочтение агентам, которые работают в песочницах или контейнерах там, где это возможно, с учётными данными, которые внедряются на сессию, а не лежат в окружении постоянно — скрапинг имён переменных маячком работает именно потому, что мы оставляем секреты валяться в окружении процессов.
Маркетплейс расширений без собственного конвейера верификации — это не маркетплейс, а файлохостинг с кнопкой установки.
Неприятный вывод
Anthropic не взломали. Взломали маркетплейс Cursor, а бренд Anthropic всё равно получил урон, потому что для разработчика расширение — это и есть продукт, кто бы ни опубликовал ту или иную сборку. В этом экономика форкнутых экосистем: самый дешёвый способ ударить по ценной цели — атаковать самый слабый реестр, которому доверяют её пользователи. ИИ-ассистенты для кодинга стоят ровно на пересечении: огромная установочная база, максимальные локальные привилегии, учётные данные повсюду и цепочка поставок, которая форкает и переиздаёт их артефакты в реестрах с очень разным уровнем безопасности.
Этот случай закончился тем, что вредоносные версии сняли примерно через одиннадцать часов, а баг в macOS ограничил ущерб. Реакция была нормальной. Проблема в архитектуре, и она не изменилась: ваш редактор — это менеджер пакетов, который запускает всё установленное от вашего имени, обновляет себя тихо и во всё большем числе конфигураций берёт пакеты из реестра, который никто в вашей security-команде никогда не оценивал. Почините правила исходящего трафика, зафиксируйте расширения и проведите инвентаризацию агентов. Следующая компрометация издателя не получит баг в регулярке, который спасёт ваших пользователей.


