trigger.dev: Cross-Tenant-Datenabfluss über SQL-Injection im `tags`-Parameter der Realtime-API

Jeder API-Schlüssel mit read:runs-Berechtigung streamt über eine SQL-Injection im tags-Parameter die TaskRun-Datensätze aller Organisationen statt nur der eigenen.

Advisory-ID: TP-2026-075
Produkt: trigger.dev (Open-Source-Plattform für Hintergrund-Jobs)
Schwachstellentyp: SQL-Injection / Cross-Tenant-Datenabfluss (CWE-89)
CVE: nicht beantragt
CVSS 3.1: 7.7 (Hoch) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N
Betroffene Versionen: <= 4.5.5
Behoben in: 4.5.6
Hersteller-Advisory: GHSA-p6j2-2fjh-vc8v
Gemeldet: 29. Mai 2026

Zusammenfassung

trigger.dev ist eine Open-Source-Plattform für Hintergrund-Jobs. Der Realtime-Endpunkt GET /realtime/v1/runs baut die ElectricSQL-where-Klausel per String-Interpolation aus dem tags-Parameter, ohne ihn zu escapen. Ein tags-Wert, der das Array-Literal schließt und eine boolesche Bedingung anhängt, hebt die daneben per AND verkettete Mandanten-Trennung auf. Jeder API-Schlüssel mit read:runs-Berechtigung streamt danach die TaskRun-Datensätze aller Organisationen statt nur der eigenen. turingpoint hat den Ablauf verifiziert und verantwortungsvoll gemeldet; der Hersteller hat ihn in 4.5.6 behoben.

Ursache

Der Endpunkt GET /realtime/v1/runs parst den tags-Parameter (apps/webapp/app/routes/realtime.v1.runs.ts) und reicht ihn an den Realtime-Client weiter. Dieser umschließt jeden Tag als '<tag>' innerhalb von "runTags" @> ARRAY[...] und fügt ihn per String-Interpolation ohne Escaping ein (apps/webapp/app/services/realtimeClient.server.ts:171). Diese Tag-Bedingung wird per AND mit der Mandanten-Trennung zu einer where-Klausel verbunden (realtimeClient.server.ts:180), die als Ganzes an ElectricSQL geht. Die Mandanten-Trennung steht korrekt im Code (realtimeClient.server.ts:168), wird aber wirkungslos, sobald der interpolierte Tag-Wert das Array-Literal schließt und eine stets wahre Bedingung anhängt. Da nur ein Projekt-Schlüssel mit read:runs nötig ist, genügt eine niedrig privilegierte, aber authentifizierte Rolle, um die Isolation zwischen Organisationen aufzuheben.

Proof of Concept

Schematisch, mit einem Projekt-Schlüssel, der nur read:runs hält:

GET /realtime/v1/runs?tags=<wert, der ARRAY[...] schliesst und OR true anhaengt>
Authorization: Bearer <read:runs key>

-> der interpolierte Tag-Wert beendet das ARRAY[...]-Literal und haengt eine
   stets wahre Bedingung an; die Antwort streamt TaskRun-Datensaetze fremder
   Organisationen statt nur der eigenen.

Weil der Tag-Wert unescaped in die where-Klausel wandert, kann er die per AND angehängte Mandanten-Trennung durch eine OR-verknüpfte, stets wahre Bedingung überstimmen.

Auswirkung

  • Lesen aller TaskRun-Datensätze sämtlicher Organisationen mit einem einzigen read:runs-Schlüssel.
  • Bruch der Mandanten-Isolation zwischen den Organisationen einer Instanz.
  • Abfluss potenziell sensibler Job-Metadaten (Tags, Status, Zeitstempel, Kennungen) fremder Mandanten.

Referenzen

Steckt so etwas in Ihrer Software?

Diese Schwachstelle hat unser Team im Rahmen seiner Arbeit gefunden. Lassen Sie Ihre Anwendungen von denselben Spezialisten prüfen, mit einem Penetrationstest von turingpoint.