awesome-everything EN
↑ Обратно к восхождению

API

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

Суть DataLoader чинит поездки в базу. Depth-лимиты, complexity scoring, persisted queries и alias-кепы останавливают атакующих, отправляющих убийственные запросы до того, как запустится хоть один резолвер.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

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

Depth limiting

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.

Связанные уроки
встречается в178
Продолжить восхождение ↑Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемость
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.