Security
9 min read
1110 views

Context Mixing: Ausnutzen von @ExecutionContext in GraphQL-Modulen

IT
InstaTunnel Team
Published by our engineering team
Context Mixing: Ausnutzen von @ExecutionContext in GraphQL-Modulen

GraphQL hat sich zur Rückgrat federated und modularisierter Datenlagen in moderner Web-Architektur entwickelt. Doch mit zunehmender Abstraktion steigen auch die Angriffsflächen. Eine solche Gefahr besteht an der Schnittstelle von Dependency Injection, asynchronem JavaScript und gemeinsam genutztem Singleton-Zustand — und sie ist gefährlicher, als die meisten Teams vermuten.

Dieser Beitrag analysiert eine kritische Klasse von Schwachstellen im Framework der GraphQL Modules, erklärt die realen Mechanismen dahinter, verbindet sie mit einem kürzlich gepatchten Node.js CVE, das die Bedrohung beweist, und gibt konkrete Schritte zum Schutz Ihrer API.


Was sind GraphQL Modules?

GraphQL Modules ist ein Toolkit, das von The Guild gepflegt wird und Teams ermöglicht, wiederverwendbare, wartbare und testbare Module für GraphQL-Server zu erstellen. Im Kern steht ein leistungsstarkes Dependency Injection-System, das Entwicklern erlaubt, “Providers” zu definieren — Dienste, die für Geschäftslogik, Datenabruf und Autorisierung verantwortlich sind.

Eines seiner nützlichsten Features ist der @ExecutionContext-Decorator.


Die Rolle von @ExecutionContext

Bei einer standardmäßigen GraphQL-Ausführung wird ein context-Objekt durch jeden Resolver gereicht. Es enthält typischerweise die Sitzung des aktuellen Nutzers, Authentifizierungstoken und Datenbankverbindungen.

Das Knifflige: Wenn Sie mit Singleton-Providern arbeiten — Diensten, die einmal für die Lebensdauer der Anwendung instanziiert werden — können Sie nicht direkt auf den Request-spezifischen Kontext zugreifen. Der @ExecutionContext-Decorator wurde entwickelt, um diese Lücke zu schließen. Er ermöglicht einem Singleton-Dienst, dynamisch auf den Kontext der aktuellen Ausführung zuzugreifen, meist durch Nutzung der asynchronen Ressourcenverfolgung von Node.js.

Wie die offiziellen GraphQL Modules-Dokumentationen beschreiben:

e “Dank des @ExecutionContext-Decorators erhält jeder Singleton-Provider Zugriff auf den GraphQL-Kontext und den operationenspezifischen Injector.”

Das klingt sauber. In der Praxis, bei gleichzeitiger Belastung, ist es eine gefährliche Waffe.


Die Schwachstelle: Context Mixing bei gleichzeitiger Belastung

Die Schwachstelle ist ein Race Condition (CWE-362), das auftritt, wenn mehrere parallele Requests einen Singleton-Dienst mit @ExecutionContext ansprechen.

Ursachen: Zustandsverschmutzung im DI-Container

Das Problem liegt in unzureichender Isolierung des Ausführungskontexts im DI-Container. Wenn zwei oder mehr Requests gleichzeitig verarbeitet werden, kann es passieren, dass die Injection des Kontexts in die gemeinsame Service-Instanz nicht korrekt synchronisiert wird.

Unter hoher Last: Stellen Sie sich folgendes Szenario vor:

  • Benutzer A (Administrator) und Benutzer B (normaler Nutzer) senden nahezu gleichzeitig Anfragen.
  • Beide rufen einen Singleton-Service auf, der @ExecutionContext nutzt.
  • Das Framework weist den Kontext von Benutzer A dem Singleton zu.
  • Der Resolver von Benutzer A erreicht einen await — eine Datenbankabfrage, API-Anfrage oder eine andere asynchrone Operation.
  • Während Benutzer A bei await pausiert, verarbeitet die Event Loop Node.jss die Anfrage von Benutzer B.
  • Da die Singleton-Instanz geteilt ist, wird die interne context-Referenz überschrieben — oder schlimmer noch, die erhöhte Berechtigungskontext von Benutzer A sickert in den Ausführungsweg von Benutzer B.

Dies ist ein klassischer Time-of-Check Time-of-Use (TOCTOU)-Bug. Der Dienst liest den Kontext zu einem Zeitpunkt, aber wenn er auf diesen Kontext für eine sensible Operation zugreift, wurde der Wert durch eine gleichzeitige Anfrage überschrieben.

Warum Node.js das verschärft

Node.js ist single-threaded, handhabt aber Parallelität durch die Event Loop und asynchrone Callbacks. Das async_hooks-Modul (und sein moderner Nachfolger AsyncLocalStorage) sind dafür gedacht, den Ausführungskontext über asynchrone Grenzen hinweg zu verfolgen. Doch jedes await schafft einen “Suspension Point” — ein Fenster, in dem die Event Loop eine andere Anfrage verarbeiten und geteilte Referenzen in einem Singleton überschreiben kann.

Die GitHub-Issues zu GraphQL Modules dokumentieren reale Fälle, in denen das versehentliche Injizieren eines session-spezifischen Dienstes in einen anwendungsweiten (Singleton) Provider zu Speicherlecks und Zustandskontamination führte. Context Mixing ist die sicherheitsrelevante Variante desselben architektonischen Fehlers.


Reale Welt: CVE-2025-59466

Dies ist kein rein theoretischer Bug. Am 13. Januar 2026 patchte das Node.js-Sicherheitsteam CVE-2025-59466 — eine kritische Schwachstelle in async_hooks, die neun Jahre im Code schlummerte.

Die Schwachstelle zeigte, dass bei einem Stack-Overflow innerhalb eines async_hooks-Callbacks (bei tief verschachtelter rekursiver Eingabe) die Laufzeit einen fatalen Fehler-Handler aufruft und den gesamten Serverprozess beendet — ohne Möglichkeit für Nutzerland-Code, den Fehler abzufangen. Jede laufende Anfrage wurde verworfen.

Relevanter für Context Mixing: Diese CVE zeigte, dass die Kontextverfolgung mit async_hooks unter feindlichen Bedingungen fragil ist. Alles, was die normale asynchrone Ausführungskette stört — extreme Verschachtelung, rekursive Auflösung oder hohe Parallelität — kann die Kontextübertragung, auf die Frameworks wie GraphQL Modules angewiesen sind, beschädigen.

Betroffene Node.js-Versionen: 8.x bis 18.x (alle End-of-Life und ungepatcht). Gepatchte Versionen: 20.20.0, 22.22.0, 24.13.0 und 25.3.0.

Zusätzlich hat das Node.js-Team die niedrigstufige async_hooks-API offiziell als Stabilität 1 - Experimentell markiert und empfiehlt die Migration zu AsyncLocalStorage. Ab Node.js 24+ wurde AsyncLocalStorage auf V8’s AsyncContextFrame neu implementiert, wodurch die Abhängigkeit von async_hooks.createHook() entfällt und diese Fragilität beseitigt wird.

Das Context Mixing in GraphQL Modules lebt in diesem selben Bereich: eine Framework-Abstraktion, die auf async_hooks-basierte Kontextisolierung vertraut, um unter gleichzeitiger Belastung standzuhalten.


Exploit-Szenario: Privilegieneskalation in einer Finanzanwendung

Stellen Sie sich eine Finanzanwendung vor, in der ein Administrator die Gehaltsabrechnung batchweise verarbeitet, während ein böswilliger Nutzer nur seine Profilseite aktualisiert.

Das Setup

Die Anwendung nutzt einen FinanceService, um Transaktionen zu autorisieren. Für die Performance ist dieser als Singleton deklariert und verwendet @ExecutionContext, um das Token des Anrufers zu ziehen:

import { Injectable, ExecutionContext, Scope } from 'graphql-modules';

@Injectable({ scope: Scope.Singleton })
class FinanceService {
  @ExecutionContext()
  private context: ExecutionContext;

  async transferFunds(amount: number) {
    // Hier liegt die Schwachstelle bei gleichzeitiger Belastung
    const token = this.context.injector.get(AUTH_TOKEN);

    return await bankApi.post('/transfer', { amount }, {
      headers: { Authorization: `Bearer ${token}` }
    });
  }
}

Der Angriffsvektor

Der Angreifer braucht keine fortgeschrittenen Fähigkeiten. Ein einfaches Skript — oder sogar Burp Suite’s Intruder —, das eine Flut von Requests während einer bekannten Admin-Session sendet, reicht aus. Ziel ist nur das Timing.

Die Kollision

  1. Benutzer A (Admin) löst eine sensible transferFunds-Mutation aus.
  2. Das Framework setzt @ExecutionContext auf den Kontext des Admins im Singleton.
  3. Der Resolver von Benutzer A erreicht den await bankApi.post(...)-Aufruf und pausiert.
  4. Die Event Loop verarbeitet die Anfrage von Benutzer B.
  5. Aufgrund des Race Conditions in der Kontextreferenz überschreibt die Ausführung von Benutzer B entweder den Kontext — oder erbt den Admin-Token von Benutzer A.
  6. Die Anfrage von Benutzer B führt transferFunds mit dem Authorization-Header des Admins aus.
  7. RBAC wird vollständig umgangen. Die Logs zeigen die Transaktion, als käme sie vom Admin.

Proof-of-Concept-Muster

Sicherheitsforscher validieren diese Bug-Klasse mit verzögerter Promise-Injektion — absichtliches Pausieren der Ausführung einer Anfrage bei einem await-Grenzpunkt, dann ein Rennen gegen eine zweite Anfrage, um zu demonstrieren, dass die Kontextreferenz im Singleton sich ändert. Unter Timing-Tests ist der Kontextwechsel bei hoher Parallelität zuverlässig reproduzierbar.


Auswirkungen

Die Schwere dieser Schwachstelle ist HOCH. Die Auswirkungen betreffen sowohl Vertraulichkeit als auch Integrität.

Risiko-Bereich Potenzielle Auswirkungen
Unbefugter Datenzugriff Nutzer können PII, Finanzdaten oder private Nachrichten anderer Nutzer einsehen
Privilegieneskalation Ein Nutzer mit niedrigen Rechten kann administrative Aktionen im Namen eines anderen durchführen
Kontenübernahme Leakte Sitzungstoken können Zugriff auch nach Ende des Race-Fensters gewähren
Manipulation der Audit-Logs Malicious mutations erscheinen in den Logs, als würden sie vom Opfer stammen

Behebung und Best Practices

1. Aktualisieren Sie GraphQL Modules

Wenn Sie eine anfällige Version verwenden, patchen Sie sofort.

npm install graphql-modules@latest
# oder
yarn add graphql-modules@latest

Der Fix beinhaltet eine Neugestaltung, wie @ExecutionContext den internen Injector verfolgt, um sicherzustellen, dass Kontextreferenzen niemals über asynchrone Grenzen hinweg geteilt werden.

Gepatchte Versionen: - graphql-modules ≥ 2.4.1 - graphql-modules ≥ 3.1.1 - @envelop/graphql-modules ≥ 9.1.0

2. Node.js aktualisieren

Auf eine der gepatchten Node.js-Versionen upgraden: 20.20.0, 22.22.0, 24.13.0 oder 25.3.0. Diese Versionen beheben die zugrunde liegende async_hooks-Fragilität. Wenn Sie auf Node.js 24+ umsteigen, basiert AsyncLocalStorage nicht mehr auf async_hooks.createHook() und ist somit weniger anfällig.

3. Operation Scope statt Singleton für auth-sensitive Dienste bevorzugen

Dies ist die robusteste Lösung. Die GraphQL Modules-Dokumentation macht klar: Der Operation Scope überschneidet sich nicht zwischen Requests. Für drei parallele Requests werden drei isolierte Service-Instanzen erstellt — eine pro Request.

import { Injectable, Scope, createModule } from 'graphql-modules';

@Injectable({ scope: Scope.Operation }) // pro Request, vollständig isoliert
class FinanceService {
  constructor(private authContext: AuthContext) {}

  async transferFunds(amount: number) {
    const token = this.authContext.token; // Sicher: nur an diesen Request gebunden
    return await bankApi.post('/transfer', { amount }, {
      headers: { Authorization: `Bearer ${token}` }
    });
  }
}

Ja, das hat einen kleinen Performance-Trade-off — Dienste werden pro Operation instanziiert, nicht wiederverwendet. Für Dienste, die Authentifizierung oder Berechtigungsentscheidungen treffen, ist das die richtige Wahl. Singletons sollten nur für wirklich stateless, request-unabhängige Dienste verwendet werden: Konfigurationsleser, Caches mit eigener Isolierung, Connection-Pool-Manager usw.

4. Authentifizierung in globale Middleware verschieben, nicht in Service-Interna

Statt, dass Dienste “User-Kontext” via Decorator “ziehen” (was auf die korrekte Verkabelung im Runtime angewiesen ist), validieren und hängen Sie Autorisierungsdaten vor der Ausführung an den DI-Container an. Ein Envelop-Plugin oder Express-Middleware ist dafür geeignet:

const useAuthentication = () => ({
  onExecute({ args }) {
    const token = args.contextValue.token;
    if (!token || !isValid(token)) {
      throw new GraphQLError('Unauthorized', {
        extensions: { code: 'UNAUTHENTICATED' }
      });
    }
  }
});

Dieses “Push”-Modell eliminiert die Möglichkeit des Context-Mixings auf Decorator-Ebene vollständig.

5. Request-Correlation-IDs für Erkennung implementieren

Selbst wenn Sie nicht sofort patchen können, können Sie Context-Mixing in Ihren Logs erkennen. Weisen Sie jeder eingehenden Anfrage eine eindeutige Request-ID zu und fügen Sie diese in alle Log-Zeilen während des Request-Lebenszyklus ein. Wenn Ihre Logs eine Request-ID mit mehreren User-IDs zeigen, liegt ein Beweis für Cross-Contamination vor.

app.use((req, res, next) => {
  req.requestId = crypto.randomUUID();
  res.setHeader('X-Request-ID', req.requestId);
  next();
});

Die größere Lektion: Gemeinsamer Zustand in asynchronen Umgebungen ist gefährlich

CVE-2025-59466 und das Risiko des Context Mixing in GraphQL Modules zeigen eine gemeinsame Wahrheit: async-Kontextverfolgung ist schwer richtig umzusetzen, und “magische” Abstraktionen darauf bauen, erben diese Fragilität.

Die Node.js-Dokumentation markiert das rohe async_hooks-API jetzt als experimentell und empfiehlt die Nutzung von AsyncLocalStorage. Das Node.js-Team rät dringend, async_hooks zu meiden. Frameworks, die vor der Reife von AsyncLocalStorage darauf gebaut haben, tragen technischen Schuldenberg, der bei hoher Parallelität Sicherheitslücken offenbaren kann.

Die klare Devise: Zustand, der einem Nutzer gehört, darf niemals in einem Singleton leben.

Wenn Sie @ExecutionContext verwenden, prüfen Sie alle Singleton-Provider, die es nutzen. Fragen Sie sich: Was passiert, wenn dieser Kontext falsch referenziert wird? Wenn die Antwort Tokens, Berechtigungen oder sensible Daten betrifft — sollte dieser Service kein Singleton sein.


Weiterführende Schritte

Der Fehler des Context Mixing ist nur einer von mehreren aufkommenden Bedrohungen im GraphQL-Ökosystem. Weitere Bereiche, die Sie in Ihrer eigenen Stack prüfen sollten, sind Query-Complexity-Attacken (unbegrenzte Resolver-Tiefe, die zu DoS führen), Batching-Exploits durch native GraphQL-Aliasierung, Exposure von Introspection in Produktion und die breitere Klasse von TOCTOU-Bugs in jedem DI-Framework, das Singleton- und Request-spezifischen Zustand über eine async-Grenze verbindet.

Ihre Resolver sind nur so sicher wie der Kontext, dem sie vertrauen.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#Context mixing, GraphQL context mixing, CVE-2026-23735, GraphQL Modules vulnerability, @ExecutionContext, GraphQL race condition, GraphQL concurrency bug, authentication context confusion, privilege escalation GraphQL, parallel request vulnerability, request context leak, security context mixup, cross-request contamination, GraphQL authorization bypass, GraphQL auth bug, GraphQL multi-tenant vulnerability, GraphQL isolation failure, async context bug, thread safety bug GraphQL, Node.js async context leak, request scope vulnerability, dependency injection context bug, GraphQL framework vulnerability, GraphQL gateway security, API context poisoning, API race condition exploit, accidental privilege escalation, low to admin escalation, context propagation bug, request lifecycle bug, GraphQL server misconfiguration, GraphQL execution pipeline bug, parallel resolver execution risk, GraphQL security 2026, GraphQL CVE analysis, GraphQL auth middleware flaw, security decorator bug, ExecutionContext exploit, cross-user data leak GraphQL, GraphQL session confusion, token mixup vulnerability, JWT context bug, access control race condition, TOCTOU GraphQL, concurrent request attack, API privilege escalation, GraphQL threat model, GraphQL pentesting, GraphQL red team, GraphQL blue team defense, fix context isolation, per-request context isolation, secure GraphQL architecture, framework-level auth bug, microservices context leak, multi-user concurrency bug, API trust boundary failure, GraphQL incident response, GraphQL patching guidance

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles