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.
Inhaltsverzeichnis
- 1. Warum offene Endpunkte in Produktion ein Risiko sind
- 2. Wie Query-Whitelisting funktioniert: Hash statt Freitext
- 3. Das Persisted-Query-Manifest als Build-Artefakt
- 4. Apollo Router Safelisting-Modus in der Praxis
- 5. Rollout-Strategie: Warn-Modus vor Enforce-Modus
- 6. Introspection in Produktion deaktivieren
- 7. Notfall-Bypass und Prozess für neue Queries
- 8. Whitelisting vor Magento GraphQL in der Praxis
- 9. Whitelisting-Strategien im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.