open atlas
↑ К треку
API API · 06 · 05

Защита сложности запросов: depth, cost, persisted queries

DataLoader чинит поездки в базу. Depth-лимиты, complexity scoring, persisted queries и alias-кепы останавливают атакующих, отправляющих убийственные запросы до того, как запустится хоть один резолвер.

API Middle ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior

DataLoader подключён, страница грузится за 80 мс. Затем исследователь безопасности присылает { user { friends { friends { friends { ... } } } } } глубиной в 12 уровней. Сервер работает 28 секунд и возвращает 502. DataLoader сделал своё дело. Проблема — сама форма запроса, а с ней DataLoader не поможет.

DataLoader запущен, но один сформированный запрос всё равно может уложить сервер до запуска первого резолвера. Этот урок про защиты, стоящие перед выполнением, — и числа, отделяющие рабочие production-конфиги от дефолтов, которые приглашают к злоупотреблениям.

Depth limiting

Почему глубина так важна, если DataLoader уже занимается батчингом? Потому что при большой глубине число листьев растёт экспоненциально — DataLoader не в состоянии отбатчить 10^22 ID. Depth-limiter обходит AST (дерево абстрактного синтаксиса) запроса и отклоняет документы, чей уровень вложенности превышает настроенный максимум. Типичный production-лимит: 7–10 уровней. Проверка работает на фазе validation — до запуска любого резолвера, до обращения к любой базе.

{ user { friends { friends { friends { ... } } } } }
  depth 1   depth 2   depth 3   depth 4

На глубине 12 с branching factor 100 (у каждого user 100 friends) число листьев — 100^12. Даже с DataLoader-батчингом это 12 батчевых запросов каждый на 10^22 ID. Depth limiting убивает это на этапе парсинга.

List-depth строже скалярной глубины. 10-уровневый запрос, где часть уровней возвращает скаляры, отличается от запроса, где каждый уровень возвращает список. Некоторые библиотеки (например, graphile/depth-limit) предоставляют отдельный maxListDepth (типично 3–4).

Complexity scoring

Complexity scoring присваивает стоимость каждому полю и суммирует по AST. Два стиля:

  • Static weights: на каждое поле директива @cost(value: Int!). Анализатор обходит AST, суммирует стоимости, умножает list-поля на их аргументы limit. Отклоняет при превышении бюджета (типично: 1000–10000 единиц).
  • Мультипликативная (модель GitHub): cost(parent) = cost(parent_fields) + sum(child.limit × cost(child_fields)). Запрос, запрашивающий 100 элементов на каждом из 5 уровней, стоит 100^5 по этой формуле — далеко за бюджетом, отклоняется при парсинге AST.

GitHub кепит стоимость запроса на 1000 и публикует её в extensions.cost. Shopify Storefront кепит на 1000 cost/запрос и 1000 cost/sec/IP.

Persisted queries (trusted documents)

Persisted queries (предсохранённые запросы) заменяют inline-текст запроса SHA-256-хешем. Клиент регистрирует известные запросы на сервере при сборке; во время запроса шлёт только хеш и переменные. Сервер выполняет сохранённый документ.

Это закрывает всю атакующую поверхность inline-запросов: произвольные клиентские запросы невозможны. Интроспекционная разведка, complexity-атаки, alias-бомбы и depth-бомбы блокируются на входе.

Tradeoff: каждый деплой клиента требует регистрации хешей. Ad-hoc инструменты (Postman, консоль браузера) перестают работать против production. Публичные API, которые не могут ограничить клиентов (GitHub, Shopify), оставляют inline-запросы открытыми, но добавляют complexity scoring.

Alias-бомбы и operation batching

Один документ может объявить сотни корневых алиасов для одного резолвера:

q1: user(id: 1) { email }
q2: user(id: 2) { email }
...
q1000: user(id: 1000) { email }

Это один валидный документ, но он выполняет 1000 вызовов резолверов. DataLoader сворачивает поездки в базу до одного батч-запроса — но число вызовов резолверов всё равно рычаг атакующего. Production-кепы: ≤20 root-алиасов на документ, ≤5–10 операций на batch-запрос.

ЗащитаЧто останавливаетКогда работает
Depth limitРекурсивные/глубокие query-бомбыValidation (до резолверов)
Complexity scoringПревышение cost-бюджетаValidation
Persisted queriesВсе произвольные inline-запросыДо парсинга
Alias capAlias-бомбыValidation
Operation batch capBatch-request амплификацияДо парсинга
DataLoaderАмплификация поездок в базуВо время разрешения
GraphQL N+1 и защита: числа
Типичный depth limit
7–10 уровней
Рекомендация list-depth
3–4
Типичный complexity-бюджет
1000–10000 единиц
GitHub per-query cost кеп
1000
Shopify Storefront per-query кеп
1000 cost units
Alias-bomb кеп (типично)
≤20 root алиасов
Operation-batch кеп (типично)
≤5–10 операций
Викторина

Какая единственная самая сильная линия защиты публичного GraphQL API от complexity-атак?

Расставь шаги по порядку

Поставь проверки безопасности, которые production GraphQL-сервер выполняет над входящим запросом:

  1. 1 Hash-lookup: это известный persisted query? Если да — принять и выполнить сохранённый документ
  2. 2 Распарсить и валидировать документ против схемы
  3. 3 Depth-анализ: отклонить, если глубина превышает максимум
  4. 4 Complexity scoring: обойти AST, просуммировать стоимости, отклонить при превышении бюджета
  5. 5 Авторизация: проверить, что клиент имеет права на операцию
  6. 6 Выполнить резолверы через DataLoader-батчевые fetcher'ы
Викторина

API-команда включает persisted queries, но оставляет inline-эндпоинт открытым для отладки. Почему это лишь немногим безопаснее отсутствия persisted queries?

Вспомните перед уходом
  1. 01
    Что делает complexity scoring, чего не делает depth limiting?
  2. 02
    Persisted queries блокируют complexity-атаки. Каков их операционный tradeoff?
Итог

DataLoader чинит N+1-проблему в рамках выполнения резолверов. Форму запроса он не затрагивает. Depth-лимиты (7–10 уровней) отклоняют рекурсивные бомбы на этапе validation. Complexity scoring (бюджет 1000–10000) отклоняет cost-overrun документы до запуска резолверов. Persisted queries закрывают всю inline-document атакующую поверхность, разрешая только pre-registered хеши. Alias-кепы (≤20) и operation-batch кепы (≤5–10) останавливают amplification-атаки, обходящие наивные per-request rate limit. Используй все слои вместе; каждый fails closed. Теперь, когда выкатываешь GraphQL API, пройдись по таблице выше и проверь, какие строки включены, — отсутствие depth-лимита и alias-кепа — два самых частых пробела, которые всплывают на production security review.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки
встречается в204

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.