GraphQL Query-Whitelisting für Produktionsumgebungen
AI generated
{ }
type
GraphQL · Whitelisting · Produktionssicherheit · Apollo Router
GraphQL Query-Whitelisting
für Produktionsumgebungen richtig einführen

Ein offener GraphQL-Endpunkt erlaubt jedem Client, beliebige Queries gegen das Schema zu formulieren, auch solche, die nie für den produktiven Einsatz gedacht waren. Query-Whitelisting kehrt dieses Prinzip um: Nur vorab bekannte, geprüfte Queries werden ausgeführt, alles andere wird abgelehnt, bevor es den Resolver erreicht.

16 Min. Lesezeit Manifest · Safelisting · Warn-Modus · Introspection GraphQL · Security · Production Ops

1. Warum offene Endpunkte in Produktion ein Risiko sind

Ein GraphQL-Schema ist über Introspection selbstbeschreibend, das macht Entwicklung komfortabel, öffnet aber gleichzeitig einem Angreifer die vollständige Landkarte aller Typen, Felder und Beziehungen. Ohne Query-Whitelisting kann jeder Client, der den Endpunkt erreicht, beliebig tief verschachtelte, beliebig komplexe Queries formulieren, die weit über das hinausgehen, was das eigentliche Frontend jemals verwenden würde. Das ist der Kernunterschied zu klassischen REST-APIs: Ein REST-Endpunkt exponiert nur die Operationen, die ein Entwickler explizit implementiert hat, ein offener GraphQL-Endpunkt exponiert alles, was das Schema theoretisch hergibt.

In Produktionsumgebungen mit sensiblen Daten oder hohem Traffic ist das ein doppeltes Risiko: zum einen können Angreifer über unerwartete Query-Kombinationen versuchen, Zugriffskontrollen zu umgehen oder Informationen abzugreifen, die für ein Frontend nie vorgesehen waren, zum anderen kann eine einzelne, absichtlich überkomplexe Query genug Backend-Last erzeugen, um den Service zu überlasten. Query-Whitelisting begrenzt die Angriffsfläche auf exakt die Queries, die ein Team vorab geprüft und freigegeben hat, unabhängig davon, was das Schema theoretisch erlauben würde.

2. Wie Query-Whitelisting funktioniert: Hash statt Freitext

Das technische Prinzip hinter Query-Whitelisting ist einfach: Statt den vollständigen Query-Text bei jedem Request zu übertragen und zu parsen, sendet der Client nur einen kurzen Hash, der eine zuvor registrierte Query eindeutig identifiziert. Der Server hält eine Zuordnungstabelle von Hash zu vollständigem Query-Text, meist als SHA-256-Digest der Query, und führt nur Anfragen aus, deren Hash in dieser Tabelle existiert. Jede Anfrage mit einem unbekannten Hash oder mit rohem Query-Text, der nicht der registrierten Version entspricht, wird abgelehnt.

Dieses Prinzip unterscheidet sich von reinen Performance-orientierten Persisted Queries dadurch, dass der Server im Whitelisting-Modus niemals eine neue, unbekannte Query registriert, selbst wenn der Client den vollständigen Query-Text mitliefert. Bei klassischen Automatic Persisted Queries würde der Server eine neue Query beim ersten vollständigen Aufruf akzeptieren und cachen. Im produktiven Whitelisting-Modus ist genau dieser Automatismus deaktiviert: Die Menge erlaubter Queries wird ausschließlich über einen kontrollierten Build- und Deploy-Prozess erweitert, niemals zur Laufzeit durch einen Client.


# This exact query text was registered in the manifest during the frontend build
query ProductDetail($sku: String!) {
  product(sku: $sku) {
    name
    price
    description
  }
}

# A request sending only the hash below is accepted if it matches the manifest entry
# POST /graphql
# { "extensions": { "persistedQuery": { "sha256Hash": "a1b2c3..." } }, "variables": { "sku": "TEST-001" } }

# Any query NOT in the manifest — even a harmless-looking variant — is rejected
query ProductDetailWithReviews($sku: String!) {
  product(sku: $sku) {
    name
    price
    reviews { rating comment }   # not part of any registered query — blocked
  }
}

3. Das Persisted-Query-Manifest als Build-Artefakt

Das Herzstück eines funktionierenden Query-Whitelisting-Setups ist das Manifest, eine Datei, die jede erlaubte Query zusammen mit ihrem Hash enthält. Dieses Manifest wird nicht von Hand gepflegt, sondern automatisiert während des Frontend-Builds generiert: Ein Tool durchsucht den Quellcode nach allen gql-Tags oder .graphql-Dateien, extrahiert die enthaltenen Operationen und berechnet für jede den SHA-256-Hash. Das Ergebnis ist eine JSON-Datei, die als Build-Artefakt zusammen mit dem Frontend deployt und dem Server bekannt gemacht wird.

Wichtig für einen zuverlässigen Whitelisting-Prozess ist, dass das Manifest bei jedem Frontend-Deploy synchron mit dem tatsächlich ausgelieferten Code aktualisiert wird. Ein veraltetes Manifest, das eine mittlerweile aus dem Frontend entfernte Query noch enthält, ist ein geringes Risiko, ein Manifest, dem eine neu hinzugekommene Query fehlt, führt dagegen sofort zu fehlschlagenden Requests im produktiven Frontend. Deshalb gehört die Manifest-Generierung in denselben CI-Schritt wie der Frontend-Build selbst, nicht in einen separaten, manuell ausgelösten Prozess.


#!/usr/bin/env bash
# generate-manifest.sh — extracts every GraphQL operation and hashes it for the manifest
set -euo pipefail

echo "[BUILD] Extracting persisted queries from source..."
npx @graphql-codegen/cli --config codegen.persisted.yml

MANIFEST_FILE="dist/persisted-queries.json"
QUERY_COUNT=$(jq 'length' "$MANIFEST_FILE")

echo "[BUILD] Generated manifest with $QUERY_COUNT registered operations"

# Fail the build if the manifest is suspiciously empty — likely a broken extraction step
if [[ "$QUERY_COUNT" -eq 0 ]]; then
  echo "[ERROR] Manifest is empty — aborting deploy" >&2
  exit 1
fi

# Publish alongside the frontend build so the server can be updated in the same deploy
cp "$MANIFEST_FILE" "dist/assets/persisted-queries.json"

{
  "format": "apollo-persisted-query-manifest",
  "version": 1,
  "operations": [
    {
      "id": "a1b2c3d4e5f6...",
      "name": "ProductDetail",
      "type": "query",
      "body": "query ProductDetail($sku: String!) { product(sku: $sku) { name price description } }"
    },
    {
      "id": "f6e5d4c3b2a1...",
      "name": "AddToCart",
      "type": "mutation",
      "body": "mutation AddToCart($sku: String!, $qty: Int!) { addToCart(sku: $sku, qty: $qty) { id } }"
    }
  ]
}

4. Apollo Router Safelisting-Modus in der Praxis

Apollo Router unterstützt Query-Whitelisting nativ über seinen Persisted-Query-Mechanismus im sogenannten Safelisting-Modus. Anders als im reinen Performance-Modus, in dem der Router unbekannte Queries noch akzeptiert, solange der vollständige Text mitgesendet wird, blockiert der Safelisting-Modus konsequent jede Anfrage, deren Hash nicht im geladenen Manifest steht, unabhängig davon, ob der Client den Query-Text mitliefert. Diese Konfiguration ist eine explizite Einstellung, die von der Standard-Performance-Optimierung klar getrennt werden muss.

Der Router lädt das Manifest entweder aus einer lokal bereitgestellten Datei oder aus einer zentralen Registry wie Apollo GraphOS Uplink, was bei mehreren Router-Instanzen hinter einem Load-Balancer sicherstellt, dass alle Instanzen dieselbe Menge erlaubter Queries kennen. Für Self-Hosted-Setups ohne Apollo-Cloud-Dienste lässt sich das Manifest auch aus einem S3-Bucket oder einem einfachen HTTP-Endpunkt laden, der bei jedem Deploy aktualisiert wird, solange der Router beim Neuladen des Manifests keinen Downtime verursacht.


# router.yaml — Apollo Router in safelisting mode: reject anything not in the manifest
persisted_queries:
  enabled: true
  safelist:
    enabled: true
    # Reject unknown queries even if the full query text is sent — no runtime registration
    require_id: true
  log_unknown: true

# Manifest source — local file updated on every frontend deploy
apq:
  router:
    cache:
      redis:
        urls: ["redis://cache:6379"]

5. Rollout-Strategie: Warn-Modus vor Enforce-Modus

Query-Whitelisting sofort im vollen Enforce-Modus zu aktivieren, ohne vorherige Beobachtungsphase, ist der häufigste Grund für einen produktiven Ausfall direkt nach der Einführung. Fast jedes gewachsene Frontend enthält Queries, die im Manifest fehlen, sei es durch eine übersehene Codepfad, ein Feature-Flag-gesteuertes Query-Fragment oder eine ältere Mobile-App-Version, die noch nicht auf das neue Manifest aktualisiert wurde. Ein verantwortungsvoller Rollout beginnt deshalb immer mit einem Warn-Modus, in dem unbekannte Queries protokolliert, aber nicht blockiert werden.

In dieser Beobachtungsphase, typischerweise ein bis zwei Wochen, sammelt das Team alle geloggten, nicht im Manifest enthaltenen Queries und entscheidet für jede einzeln, ob sie legitim ist und ergänzt werden muss, oder ob sie tatsächlich unerwünscht war und ausbleiben sollte. Erst wenn die Warn-Logs für mehrere Tage in Folge leer oder auf bekannte, tolerierte Ausnahmen beschränkt sind, wechselt das Team in den vollen Enforce-Modus. Dieser zweistufige Rollout ist bei jedem Query-Whitelisting-Projekt Pflicht, unabhängig davon, wie sorgfältig das initiale Manifest wirkt.

6. Introspection in Produktion deaktivieren

Query-Whitelisting allein reicht nicht aus, wenn Introspection weiterhin aktiv bleibt, denn Introspection-Queries selbst können ebenfalls whitelisted sein oder, schlimmer, versehentlich vom Whitelisting ausgenommen werden. In den meisten produktiven GraphQL-Setups sollte die Introspection-Query __schema in Produktion komplett deaktiviert werden, unabhängig vom Whitelisting-Status, weil sie einem Angreifer sonst weiterhin die vollständige Schema-Struktur verrät, selbst wenn er keine beliebigen Business-Queries mehr ausführen kann.

Der Kompromiss dabei: Interne Entwicklungs- und Staging-Umgebungen brauchen Introspection weiterhin für Tools wie GraphiQL, Codegen und Schema-Diffing. Die gängige Lösung ist eine umgebungsabhängige Konfiguration, die Introspection nur in Produktion sperrt, kombiniert mit einem separaten, authentifizierten Introspection-Endpunkt für interne CI-Pipelines, die das Schema für Codegen-Zwecke weiterhin brauchen, ohne ihn öffentlich zugänglich zu machen.


// apollo-server-config.js — introspection locked down by environment, not by guesswork
const { ApolloServer } = require('@apollo/server');

const isProduction = process.env.NODE_ENV === 'production';

const server = new ApolloServer({
  schema,
  // Disabled in production, kept on for staging/dev so Codegen and GraphiQL still work
  introspection: !isProduction,
  plugins: [
    {
      async requestDidStart() {
        return {
          async didResolveOperation({ request }) {
            if (isProduction && request.operationName === 'IntrospectionQuery') {
              // Defense in depth — reject even if introspection was accidentally left on
              throw new GraphQLError('Introspection is disabled in production');
            }
          },
        };
      },
    },
  ],
});

7. Notfall-Bypass und Prozess für neue Queries

Ein starres Query-Whitelisting-System ohne definierten Notfall-Prozess führt in der Praxis dazu, dass Teams unter Zeitdruck das gesamte Whitelisting deaktivieren, statt eine einzelne fehlende Query gezielt zu ergänzen. Besser ist ein dokumentierter, schneller Weg, eine neue Query kurzfristig freizugeben: ein CI-Job, der eine einzelne Query anhand ihres Hash und Textes manuell zum aktiven Manifest hinzufügt, mit verpflichtendem Vier-Augen-Review, aber ohne einen vollständigen Frontend-Deploy-Zyklus abzuwarten.

Ebenso wichtig ist ein Prozess für den umgekehrten Fall: eine Query, die zwar im Manifest steht, sich aber als fehlerhaft oder als Sicherheitsrisiko herausstellt, muss sich sofort aus dem aktiven Manifest entfernen lassen, ohne auf den nächsten regulären Deploy zu warten. Beide Prozesse, Notfall-Hinzufügen und Notfall-Entfernen, sollten getestet und dokumentiert sein, bevor Query-Whitelisting überhaupt in den Enforce-Modus wechselt, denn im echten Notfall ist keine Zeit, einen ungetesteten Prozess erstmals auszuprobieren.

8. Whitelisting vor Magento GraphQL in der Praxis

Magentos eigener GraphQL-Endpunkt bringt kein natives Query-Whitelisting mit, Automatic Persisted Queries lassen sich zwar über Community-Erweiterungen oder einen vorgeschalteten Apollo Router nachrüsten, echtes Safelisting erfordert aber in jedem Fall eine zusätzliche Komponente vor dem eigentlichen Magento-Endpunkt. In der Praxis übernimmt das meist ein Apollo Router oder ein vergleichbares Gateway, das Requests validiert, bevor sie überhaupt an Magento weitergeleitet werden, wodurch Magento selbst unverändert bleibt und nur geprüfte Anfragen erreicht.

Für Headless-Storefronts mit mehreren Frontend-Typen, etwa einer Web- und einer Mobile-App, die dasselbe Magento-Backend nutzen, empfiehlt sich ein gemeinsames Manifest, das während jedes Frontend-Builds aktualisiert wird, aber pro Client-Typ um dessen spezifische Queries erweitert wird. So bleibt die Angriffsfläche minimal, ohne dass ein Frontend-Team versehentlich Queries eines anderen Teams blockiert, weil beide Teams zum selben, zentral gepflegten Manifest beitragen.

9. Whitelisting-Strategien im Vergleich

Die folgende Tabelle vergleicht die gängigen Ansätze, um in Produktion nur bekannte Queries zuzulassen.

Strategie Neue Queries erlauben Sicherheitsniveau
Offener Endpunkt, kein Whitelisting Jederzeit, unkontrolliert Niedrig, volle Angriffsfläche
Automatic Persisted Queries (APQ) Automatisch beim ersten vollständigen Aufruf Mittel, nur Performance-Fokus
Manifest-basiertes Safelisting Nur über kontrollierten Build/Deploy Hoch, volle Kontrolle
Safelisting + Introspection-Sperre Nur über kontrollierten Build/Deploy Sehr hoch, empfohlener Standard

Reine Automatic Persisted Queries lösen ein Performance-Problem, kein Sicherheitsproblem, weil sie neue Queries weiterhin akzeptieren. Für produktive APIs mit echten Sicherheitsanforderungen ist manifest-basiertes Safelisting kombiniert mit einer Introspection-Sperre der empfohlene Standard, weil beide Maßnahmen zusammen die Angriffsfläche auf exakt das begrenzen, was ein Team bewusst freigegeben hat.

Mironsoft

GraphQL-Produktionshärtung und API-Sicherheit

Euren GraphQL-Endpunkt für Produktion absichern?

Wir richten Query-Whitelisting mit Manifest-Generierung, Warn-vor-Enforce-Rollout und Introspection-Sperre ein, inklusive Notfall-Prozess für kurzfristige Query-Freigaben.

Manifest-Setup

Automatisierte Query-Extraktion und Hash-Generierung im CI-Build einrichten

Rollout-Begleitung

Warn-Modus überwachen und sicheren Wechsel in den Enforce-Modus begleiten

Notfall-Prozess

Getesteten Bypass-Workflow für kurzfristige Query-Ergänzungen aufbauen

10. Zusammenfassung

Query-Whitelisting begrenzt einen produktiven GraphQL-Endpunkt auf exakt die Queries, die ein Team bewusst geprüft und freigegeben hat, statt das gesamte Schema für beliebige Client-Anfragen offen zu lassen. Ein automatisiert generiertes Manifest, das bei jedem Frontend-Build aktualisiert wird, bildet dabei die technische Grundlage, während Apollo Router mit seinem Safelisting-Modus die Durchsetzung übernimmt und jede Anfrage mit unbekanntem Hash konsequent blockiert.

Ein sicherer Rollout beginnt immer mit einem Warn-Modus, der fehlende Queries sichtbar macht, ohne sie sofort zu blockieren, und wechselt erst nach einer sauberen Beobachtungsphase in den vollen Enforce-Modus. Kombiniert mit einer Introspection-Sperre in Produktion und einem getesteten Notfall-Prozess für kurzfristige Query-Freigaben entsteht ein GraphQL-Setup, das Angreifern die vollständige Schema-Landkarte entzieht und gleichzeitig legitimen Frontend-Teams keine unnötige Reibung im Alltag verursacht.

GraphQL Query-Whitelisting für Produktion — Das Wichtigste auf einen Blick

Manifest-Generierung

Automatisiert im CI-Build, synchron mit jedem Frontend-Deploy, niemals von Hand gepflegt.

Safelisting-Modus

Blockiert jeden Hash, der nicht im Manifest steht, auch bei mitgesendetem Query-Text.

Warn-vor-Enforce

Erst protokollieren, dann blockieren, um produktive Ausfälle durch übersehene Queries zu vermeiden.

Introspection-Sperre

In Produktion deaktivieren, in Staging und CI für Codegen weiterhin verfügbar halten.

11. FAQ: GraphQL Query-Whitelisting

1Whitelisting vs. APQ?
APQ akzeptiert automatisch neue Queries für Performance. Whitelisting lehnt jede nicht vorab registrierte Query konsequent ab.
2Wie das Manifest generieren?
CI-Schritt durchsucht Quellcode nach Operationen, berechnet SHA-256-Hashes, schreibt beides in eine JSON-Datei.
3Warum erst Warn-Modus?
Gewachsene Frontends enthalten fast immer fehlende Queries im initialen Manifest. Warn-Modus deckt sie ohne Blockade auf.
4Whitelisting allein ausreichend?
Nein, immer mit Introspection-Sperre kombinieren, sonst bleibt die Schema-Struktur über __schema auslesbar.
5Fehlende Query im Manifest?
Wird im Enforce-Modus abgelehnt. Notfall-Prozess mit Vier-Augen-Review erlaubt kurzfristige Ergänzung.
6Apollo Router nativ unterstützt?
Ja, über den Safelisting-Modus, explizit von der Performance-Optimierung getrennt konfiguriert.
7Mehrere Frontend-Typen?
Gemeinsames, zentral gepflegtes Manifest, zu dem jedes Team seine Queries beim Build beiträgt.
8Magento GraphQL nativ?
Nein, erfordert vorgeschaltete Komponente wie einen Apollo Router zur Request-Validierung.
9Bremst es die Entwicklung?
Bei korrektem Setup nicht, da die Manifest-Generierung automatisiert im Build läuft.
10Dauer der Warn-Modus-Phase?
Typischerweise ein bis zwei Wochen, bis die Logs mehrere Tage leer oder auf bekannte Ausnahmen beschränkt sind.