Bezpečnost GraphQL API

Proč jsou GraphQL API bezpečnostně specifická

GraphQL přináší flexibilní dotazovací jazyk, kde si klient sám určuje strukturu dat. To zásadně mění bezpečnostní model oproti RESTu: granularita (autorizace až na úroveň jednotlivých polí), kompozice (fragmenty, aliasy, unie), resource amplification (hloubka a šířka dotazů) a transport (často dlouhodobě trvající spojení pro subscription). Cílem tohoto článku je shrnout osvědčené postupy, jak navrhnout, implementovat a provozovat GraphQL API s robustními kontrolami proti zneužití a úniku dat.

Model hrozeb a bezpečnostní cíle

  • Důvěrnost: Zabránit exfiltraci dat skrze volitelnou strukturu dotazů a introspekci.
  • Integrita: Zajistit, aby mutace respektovaly obchodní pravidla, transakční hranice a jemnozrnná oprávnění.
  • Dostupnost: Omezit DoS útoky využívající náročné dotazy, vzory n+1, masivní aliasování a subscription fan-out.
  • Odpovědnost: Transparentní audit, korelace dotazů s identitou uživatele a operationName.

Autentizace a transportní vrstvy

  • TLS všude: Vynucujte HTTPS i pro WebSocket (wss://). Zabraňte downgradu a použití slabých šifer.
  • OAuth 2.0 / OIDC: Přenášejte identity jako Bearer tokeny (JWT/PASETO) v hlavičce Authorization; nepřenášejte session v URL.
  • Krátká životnost a rotace tokenů: Access tokeny s krátkou životností, refresh tokeny řízené politikami (revoke, blacklisty podle jti).
  • mTLS / NULA: Pro B2B integrace zvažte vzájemné TLS a vazbu tokenu na komunikační kanál (DPoP/mtls-bound tokens).

Autorizace: od schématu po resolver

Autorizaci navrhujte deklarativně a konzistentně, ideálně přímo na úrovni schématu.

  • Model oprávnění: RBAC (role-based), ABAC (atributové), případně hybridní model (role + podmínky nad kontextem a daty).
  • Per-field enforcement: Oprávnění vynucujte na každém poli (resolveru), nejen u kořenových typů Query/Mutation.
  • Directive-based guardy: Vlastní direktivy (například @auth(role: "admin")) nebo knihovny jako GraphQL Shield/Envelop s pravidly aplikovanými na schéma.
  • Row- a column-level security: Omezte přístup k záznamům (RLS v databázi) a ke citlivým sloupcům; autorizace není pouze v aplikační vrstvě.
  • Odlíšení chyb: Neposkytujte rozdílné chybové zprávy, které by odhalovaly existenci či neexistenci objektů bez oprávnění.

Introspekce a schéma: co (ne)prozrazovat

  • Řízená introspekce: V produkčním prostředí zvažte vypnutí introspekce anonymním uživatelům nebo její povolení pouze privilegovaným rolím / neveřejným endpointům (například dump schématu při build-time pro nástroje místo runtime introspekce).
  • Operation whitelisting: Persistované dotazy (APQ/whitelist) – klient posílá pouze hash dotazu, server odmítne „neznámé“ operace.
  • Verzování schématu: Stabilní kontrakty, anotace deprecation a telemetry pro bezpečné odstraňování nechtěných polí.

Omezení složitosti dotazů (prevence DoS)

  • Depth limit: Maximální hloubka AST (například 8–12) s výjimkami pro bezpečné typy.
  • Complexity scoring: Váhy polí (například cost: O(1), O(n), O(n log n)) a per-request budget; odmítnout dotazy překračující limit.
  • Aliasy a ochrana proti batchingu: Limit počtu aliasů a opakování stejného pole; detekce „amplifikačních“ vzorů.
  • Timeouty a maxTokens: Časové limity resolverů a limity velikosti payloadu/AST; circuit breakers na úrovni gateway.
  • Rate limiting a throttling: Per IP/klient/uživatel; kombinovat s token bucket nebo Leaky Bucket na reverse proxy.

Validace vstupů a typová bezpečnost

  • Custom scalary: Email, URL, UUID, SafeInt, NonEmptyString s validační logikou (runtime i schema-first).
  • Whitelisting hodnot: Enumy místo volných stringů; normalizace Unicode, zákaz neviditelných znaků a NUL znaků.
  • Žádné „raw“ dotazy: Nikdy neskládejte SQL či NoSQL dotazy přímo ze vstupních stringů uživatele. Používejte parametrizaci a query buildery.
  • Velikost vstupů: Omezte velikost proměnných, počet prvků v seznamech a hloubku vnoření vstupních typů.

Chyby, logování a úniky informací

  • Maskování chyb: Do errors[].message posílejte neutrální zprávy; interní stacktrace pouze do serverových logů.
  • Obohacení o kód chyby: V elementu extensions.code vracejte strojově čitelný kód (například FORBIDDEN, BAD_USER_INPUT).
  • PII hygiene: Nelogujte kompletní dotazy s citlivými proměnnými; maskujte hodnoty (například hesla, tokeny, čísla karet).
  • Sledovatelnost (traceability): Vyžadujte operationName, korelační ID a logujte metriky (délka dotazu, hloubka, complexity score, počet volání resolverů).

N+1 problém, cache a resource amplification

  • Dataloader pattern: Batchujte přístupy do datových zdrojů (per request context) a předcházejte exponenciálnímu růstu počtu dotazů.
  • Cache vrstvy: Per-request cache (na úrovni resolverů), aplikační cache (TTL) a bezpečná CDN cache pouze pro public data. U privátních odpovědí nikdy necacheujte bez kontextu identity.
  • Limitované zobrazení: Omezení velikosti stránky, kursory (Relay) a serverové hard caps i při volbě klienta.

CORS, CSRF a bezpečnost v prohlížeči

  • CORS allowlist: Explicitní seznam povolených domén (allowed origins); nikdy nepovolujte * v kombinaci s credentials.
  • CSRF rizika: GraphQL využívající cookie-based session vyžaduje anti-CSRF tokeny u mutací; u bearer tokenů v hlavičce je riziko menší, ale validujte hlavičky Origin a Referer.
  • Bezpečnostní hlavičky: Content-Security-Policy, Referrer-Policy, X-Content-Type-Options a Strict-Transport-Security pro konzolová UI (GraphiQL/Apollo Sandbox).

Uploady, soubory a binární data

  • Multipart handling: Omezte počet souborů, velikost a typ MIME; skenujte na malware; ukládejte mimo aplikační server.
  • Dočasné URL: Generujte krátkodobě platné podepsané URI (pre-signed URL) a nezkoušejte zpracovávat velké streamy přes GraphQL resolver.

Subscriptions a bezpečnost WebSocket

  • Handshake autentizace: Ověřujte token při connection_init; pravidelně reautentizujte u dlouho trvajících spojení.
  • Autorizace per-event: Kontrolujte oprávnění při každém publikování (nejen při subscribování). Zabraňte únikům přes topic wildcard.
  • Backpressure a limity: Omezte počet simultánních subscription na uživatele či klienta a velikost fronty zpráv.

Federace, gateway a hranice důvěry

  • Apollo Federation / Schema stitching: Gateway je povrch útoku – aplikujte limity hloubky a komplexity již na hranici.
  • Důvěra mezi subgrafy: Předávané informace @requires/@key mohou odkrývat interní identifikátory; minimalizujte citlivá pole v subgrafech.
  • Autorizace na hraně: Vynucujte základní politiku v gateway (například denylist), detailní oprávnění řešte v doménových službách.

Bezpečný vývoj a testování (SDLC)

  • Schema linting: Zakazujte „leakující“ typy, nepoužívané kořeny, příliš generické scalary; vyžadujte @deprecated s uvedeným důvodem.
  • SAST/DAST pro GraphQL: Používejte statické skenery schématu a dynamické testy (fuzzing proměnných, mutací, fragmentů).
  • Contract testing: Persistované dotazy jako kontrakty – CI zakáže breaking changes bez migračního plánu.
  • Chaos a zátěžové testy: Testujte limity hloubky, aliasů, chování rate-limitů a odolnost resolverů na časové limity.

Provoz, observabilita a reakce na incidenty

  • Metriky: p50/p95/p99 latence operací, počet volání resolverů na dotaz, hloubka a complexity score, chybovost dle kódu.
  • Rozšířený audit: Logujte operationName, identitu uživatele, verzi schématu a vypočtené náklady; u citlivých akcí logujte i stav před a po.
  • WAF/GraphQL firewally: Schémově uvědomělé filtry (využívající AST) jsou účinnější než klasické signatury.
  • Runbooky: Rychlá implementace denylistů, přísnějších limitů, vypnutí introspekce, odpojení vybraných klientů, rotace klíčů.

Časté anti-patterny

  • „Všechno přes jeden admin token“: Nevytvářejte z serveru superuživatele vůči databázi; používejte role a RLS.
  • Veřejná introspekce v produkci: Nepotřebně prozrazuje interní model a názvosloví.
  • Bez limitů hloubky nebo komplexity: Otevírá cestu levným DoS útokům.
  • Chybové zprávy obsahující interní detaily: Stacktrace a SQL chyby ve veřejných odpovědích.
  • Cacheování privátních odpovědí v CDN: Riziko úniku dat mezi tenantry.

Příklad politiky složitosti (ilustrace)

{ getUser(id: ID!): User @cost(value: 5) } type User { id: ID! @cost(value: 1) name: String! @cost(value: 1) posts(limit: Int): [Post]! @cost(value: 2, multipliers: ["limit"]) } type Post { id: ID! @cost(value: 1) title: String! @cost(value: 1) comments(limit: Int): [Comment]! @cost(value: 3, multipliers: ["limit"]) } 

Server při validaci vypočítá cost na základě AST a odmítne dotazy přesahující per-tenant rozpočet.

Bezpečnostní checklist pro GraphQL

  • Transport a autentizace: HTTPS/WSS, krátké tokeny, žádné tokeny v URL, reautentizace WebSocket spojení.
  • Autorizace: Field-level guardy (direktivy/middleware), RLS, neutrální chybové zprávy bez diskriminace.
  • Introspekce: Vypnout nebo omezit, preferovat persistované dotazy.
  • Limity: Hloubka a komplexita, počet aliasů, timeouty, velikost payloadu, omezení rychlosti.
  • Validace: Custom scalary, enumy, omezení velikostí, parametrizované dotazy do DB.
  • Chyby a logy: Maskovat detaily, kód chyby v extensions, PII hygiene, korelace logů.
  • Výkon: Dataloader, stránkování s limity, cache respektující identitu.
  • Subscriptions: Autentizační handshake, autorizace per-event, limity spojení a front.
  • Federace: Limity a autorizace v gateway, minimalizace důvěry mezi subgrafy.
  • Provoz: AST-aware WAF, metriky, runbooky pro incidenty.

Závěr

GraphQL zvyšuje agilitu, ale zároveň klade zvýšené nároky na disciplinovanou bezpečnost. Úspěch spočívá v field-level autorizaci, řízené introspekci a důsledném omezení složitosti dotazů. V kombinaci s bezpečným SDLC, observabilitou a runbooky pro incidenty lze dosáhnout API, které je zároveň flexibilní pro vývojáře a odolné vůči moderním útokům.