Disclaimer: исследование проводилось исключительно в образовательных целях. Все найденные уязвимости были задокументированы. Никакие данные третьих лиц не были скомпрометированы. Автор не несёт ответственности за использование описанных техник.
Вступление
Cursor — это AI-редактор на базе VS Code, который обрабатывает миллионы строк кода разработчиков через свои серверы. Возникает естественный вопрос: насколько надёжна его серверная модель авторизации, особенно между бесплатными пользователями и доступом к мощным моделям вроде Claude 4 Opus?
Спойлер: серьёзного обхода защиты обнаружить не удалось — Cursor хорошо защищён. Однако в ходе анализа были выявлены 4 уязвимости класса CVE. Полностью реверс-инжинирингом восстановлен API-интерфейс, извлечены protobuf-схемы из 1,1 млн строк минифицированного JavaScript, а также обнаружен скрытый dev-бэкдор на production-серверах.
Разведка: декомпиляция клиента
Cursor построен на Electron и использует архитектуру VS Code. Весь клиентский код упакован в один JavaScript-бандл объёмом 1,144,696 строк. Этот файл стал основным источником информации: в нём содержатся protobuf-определения, URL API, логика OAuth, feature-флаги Statsig и маршрутизация моделей.
Извлечение protobuf-схем
Cursor использует Connect-RPC — современный RPC-фреймворк на основе HTTP/2. Все сервисы описаны прямо в JS-бандле. С помощью комбинации grep, sed и ручного анализа удалось извлечь полную схему, включая запрос AgentRunRequest.
Ключевое открытие: поле devRawModelSlug (номер 18) отсутствует в клиентском коде, но сервер его принимает и обрабатывает. Это стало одной из уязвимостей.
Извлечение токенов
Аутентификация в Cursor хранится в локальной SQLite-базе. Основной токен — JWT, содержащий стандартные claims. Важно: в токене нет информации о подписке. Проверка тарифного плана происходит исключительно на сервере.
Архитектура
Протокол: Connect-RPC
Connect-RPC передаёт protobuf-сообщения через HTTP/2 с использованием фреймов:
- flags=0x00 — фрейм с данными
- flags=0x02 — трейлер с метаданными или ошибками в формате JSON
Checksum-алгоритм
Cursor применяет кастомный checksum для валидации запросов. Алгоритм был реверс-инжинирингом извлечён из файла workbench.desktop.main.js. Он основан на временной метке и является детерминированным, что позволяет легко воспроизвести его.
Полная карта API-surface
gRPC-сервисы (Connect-RPC)
Основные сервисы:
- agent.v1.AgentService — ServerStreaming, GetDefaultModelForCli, UploadConversationBlobs
- aiserver.v1.DashboardService — ActivatePromotion, GetAllowedModelIntents
- aiserver.v1.BackgroundComposerService — StartBackgroundComposerFromSnapshot, AttachBackgroundComposer, ListBackgroundComposers
REST-эндпоинты
Ключевые:
- /auth/full_stripe_profile — профиль подписки
- /auth/start-subscription-now — активация подписки
- /user — информация о пользователе
Statsig Feature Gates
Из исходного кода извлечено более 40 feature-флагов, управляющих поведением приложения.
CVE-1: Prototype Pollution (CWE-1321)
Уровень серьёзности: Средний (DoS, потенциальная эскалация)
Эндпоинт: POST /auth/start-subscription-now
Воздействие: Сервер возвращает 500 вместо 400, что указывает на загрязнение цепочки прототипов.
JSON-эндпоинты Cursor не санитизируют поля __proto__ и constructor. При отправке таких полей сервер падает с ошибкой 500, а не возвращает корректный 400-й статус.
Тесты показали, что все варианты с __proto__ вызывают 500-ю ошибку. Это означает, что загрязнение происходит до проверки подписки.
Аналогичное поведение наблюдается на эндпоинте ActivatePromotion, где pollution меняет путь валидации.
Последствия:
- DoS: любой аутентифицированный пользователь может вызвать сбой на биллинговых эндпоинтах
- Потенциальная эскалация: если загрязнение затронет общее состояние, возможна эксплуатация (маловероятно, но зависит от архитектуры)
CVE-2: devRawModelSlug — скрытый бэкдор (CWE-489)
Уровень серьёзности: Средний (активный отладочный код в production)
Поле: AgentRunRequest.dev_raw_model_slug (поле 18)
Воздействие: наличие механизма прямого обхода маршрутизации моделей в боевом окружении.
Обнаружение
При тестировании всех полей AgentRunRequest (1–50) поле 18 ведёт себя иначе:
- Отсутствует в клиентском коде — не найдено при поиске по 1,1 млн строк
- Сервер его парсит и валидирует, возвращая специфическую ошибку
- Проверка происходит до проверки тарифа: при f3=default и f18=gpt-4o сервер отвечает ошибкой по devRawModelSlug, а не plan_block
- Поведение одинаково на всех серверах
Почему это CWE-489
Поле devRawModelSlug — это механизм для разработчиков, позволяющий напрямую указать модель, минуя маршрутизацию. В production он должен быть отключён, но:
- Код обработки присутствует в production-бинарнике
- Сервер парсит и валидирует поле
- Ошибка раскрывает имя поля, выдавая внутреннюю архитектуру
- При ошибке конфигурации или активации feature-флага поле может стать рабочим
CVE-3: Раскрытие заголовка внутреннего сервиса (CWE-200)
Эндпоинт: GET /agent.v1.AgentService/GetAllowedModelIntents
Воздействие: раскрытие архитектуры внутреннего service mesh.
При вызове метода с обычным токеном сервер возвращает "Invalid internal service header", а не стандартные 401 или 404. Это раскрывает:
- Эндпоинт существует
- Используется service-to-service аутентификация
- Есть отдельный механизм авторизации для внутренних сервисов
Брутфорс заголовков (25+ комбинаций) не дал доступа — внутренний токен не зависит от пользовательского.
CVE-4: Инъекция полей Protobuf и путаница с wire-типами
7.1 Дублирование поля (Double Field Injection)
Protobuf позволяет отправить одно поле дважды. По спецификации, последнее значение должно перезаписать предыдущее. Тест показал, что сервер корректно обрабатывает последнее значение. Однако он не отклоняет дубли — это нарушение строгого парсинга.
7.2 Путаница с wire-типами
При отправке model_id (обычно строка, wire type 2) как varint (wire type 0) сервер отклоняет запрос с ошибкой "parse binary: illegal tag". Парсер оказался достаточно строгим.
7.3 Инъекция subagent-полей
Поля 14 и 15 (selected_subagent_models и selected_subagent_model_details) принимают премиальные модели без проверки подписки. Запрос проходит, но в ответе используется модель по умолчанию (Codex 5.3). Поля игнорируются при маршрутизации, но не валидируются — это увеличивает поверхность атаки при будущих изменениях.
Протестированные векторы без результата
Полный список проверенных, но нерабочих векторов:
- Подбор названий моделей (42 варианта) — блокируется проверкой тарифа
- Манипуляция JWT — claims не содержат план, проверка идёт по БД
- OAuth callback injection — nonce валидируется
- Replay Stripe webhook — требуется подпись
- Host header override — мешает балансировщик
- gRPC Server Reflection — отключено
- Race condition на подписку — проверка атомарна
Что Cursor делает правильно
Cursor демонстрирует зрелый подход к безопасности:
- Проверка тарифа на сервере: JWT не содержит привилегий, проверка происходит по БД при каждом запросе.
- Строгий парсинг protobuf: wire type confusion отклоняется, неизвестные поля не влияют на маршрутизацию.
- Валидация подписи Stripe: webhook защищён от replay-атак.
- OAuth nonce: WorkOS проверяет nonce, предотвращая инъекции.
- Единая проверка модели: поля model_details и requested_model проходят одинаковую валидацию.
- Connect-RPC вместо raw gRPC: упрощает аудит и мониторинг.
Выводы и рекомендации
Найденные уязвимости
CURSOR-2025-001: Prototype Pollution — __proto__ вызывает 500 на биллинговых эндпоинтах
CURSOR-2025-002: Active Debug Code — devRawModelSlug парсится в production
CURSOR-2025-003: Info Disclosure — GetAllowedModelIntents раскрывает service mesh
CURSOR-2025-004: Improper Input Validation — subagent-поля не валидируются
Рекомендации для Cursor
- Санитизировать __proto__ и constructor при парсинге JSON. Использовать Object.create(null) или библиотеки вроде secure-json-parse.
- Удалить devRawModelSlug из production-схемы или не возвращать имя поля в ошибках.
- Унифицировать ошибки авторизации: GetAllowedModelIntents должен возвращать 404, а не специфическое сообщение.
- Валидировать все proto-поля: отклонять запросы с неожиданными полями в selected_subagent_models и selected_subagent_model_details.
Инструментарий
Исследование проведено с помощью:
- Python + httpx (HTTP/2 клиент)
- Ручная сериализация protobuf (без .proto файлов)
- grep / sed (анализ JS)
- SQLite3 (извлечение токенов)
Cursor — один из самых безопасных AI-продуктов, которые мне приходилось исследовать. Проверка тарифа полностью серверная, JWT не содержит привилегий, парсинг protobuf строгий. Тем не менее, prototype pollution и наличие dev-бэкдора в production — реальные проблемы, требующие исправления.
Если вы разрабатываете SaaS с серверной авторизацией, используйте этот чеклист:
- ✅ Не храните привилегии в JWT
- ✅ Проверяйте план из БД на каждый запрос
- ✅ Используйте строгий proto parsing
- ❌ Не оставляйте dev-поля в production proto
- ❌ Не раскрывайте внутренние имена через ошибки
- ❌ Санитизируйте __proto__ в JSON