Zum Inhalt springen

Sicherheitsgrundlagen

Ist ein offener API-Endpunkt ein Sicherheitsproblem? Sieh ins JSON

Dein Scan hat einen offenen API-Endpunkt gemeldet. Ob das zählt, hängt davon ab, was zurückkam, und die meisten hier gehörten der Plattform.

Vlad Tkachenko12 Min. Lesezeit
Zwei gleiche Klappen in einer Wand, aus jeder ragt ein Blatt. Auf dem einen eine Reihe Schalter, auf dem anderen eine Spalte Gesichter.

Kurz gesagt

  • Ein offener API-Endpunkt heißt: ein Pfad aus dem Code deiner App hat eine Anfrage ohne Login beantwortet, und zurück kam JSON.
  • Von denen, die wir über 30.926 Apps fanden, hatte sieben von zehn die Plattform hinterlegt, auf der die App veröffentlicht wurde, und sie geben Einstellungen zurück.
  • Entscheidend ist die Frage, ob irgendetwas in der Antwort eine Person beschreibt. Eine Preisliste ist absichtlich öffentlich. Eine Kundenliste war es nie.

Dein Scan kam mit einer Zeile zurück, die nicht wie ein Urteil klingt: einige API-Endpunkte antworten ohne Login. Vielleicht hat es dir auch jemand direkter gesagt und von einem offenen API-Endpunkt gesprochen. Darunter steht eine Adresse, und es ist gut möglich, dass du sie nicht wiedererkennst.

Hier ist der Teil, den Ratgeber um Ratgeber falsch darstellt: der übliche Rat zu diesem Befund lautet, einen Login vor deinen Endpunkt zu setzen, und bei den meisten Apps, die wir gescannt haben, gibt es gar keinen Endpunkt von dir, vor den man einen setzen könnte. Sieben von zehn der gefundenen gehören der Plattform, auf der die App veröffentlicht wurde. Diesen Befund zu lesen beginnt also damit, herauszufinden, wessen Adresse das ist, und die Antwort steht meistens schon in der ersten Zeile dessen, was zurückkommt.

Was ein offener API-Endpunkt in deinem Bericht bedeutet

Es bedeutet, dass ein Pfad aus dem Code deiner App eine Anfrage beantwortet hat, die keinen Login mitführte, und dass JSON zurückkam.

Drei Schritte, und keiner davon ist raffiniert. Wir laden deine App in einem Browser, so wie ein Besucher es tut, und lesen den Code, den dieser Browser heruntergeladen hat. Darin stehen die Adressen, die deine App aufruft, wenn sie Daten braucht. Dann fragen wir jede einzelne nach Daten, ohne Konto, ohne Cookie und ohne Schlüssel. Antwortet eine Adresse mit JSON, das keine Fehlermeldung ist, landet sie im Bericht.

Das hält fest, was passiert ist. Es ist kein Urteil über die Daten, denn nichts, was außerhalb deiner App steht, kann erkennen, ob eine Liste von Dingen eine Liste öffentlicher Dinge ist. Deshalb ist der Befund so formuliert, wie er formuliert ist: diese Endpunkte gaben Daten ohne jede Authentifizierung zurück, das kann bei öffentlichen Daten so gewollt sein, prüfe, dass keiner davon private Informationen preisgibt.

Die Adressen stammen aus deinem eigenen Code, und jede wird ohne alles gefragt. Was antwortet, landet im Bericht.

Der Bericht reicht dir also eine Liste von Adressen zum Öffnen. Jede einzelne davon gehört in einem abgemeldeten Browser geöffnet, und die ersten Zeilen der Antwort entscheiden die Sache.

Ist jeder offene API-Endpunkt ein Sicherheitsproblem?

Nein. Es gibt drei Sorten, und nur die dritte ist ein Problem.

Denk an ein Mehrfamilienhaus. Manches darin ist für jeden gedacht, der hereinkommt: die Öffnungszeiten an der Tür, die Fluchtwege, der Aushang über die Aufzugwartung am Dienstag. Irgendwo im selben Haus steht ein Aktenschrank mit den Daten der Mieter. Vom Flur aus sind ein Schwarzes Brett und ein unverschlossener Aktenschrank beide nur Mobiliar, das man öffnen kann, und auseinanderhalten kann man sie nur, indem man liest, was auf dem Papier steht.

Was geantwortet hatWessen Adresse ist dasWo du stehst
Plattform-Einstellungen: Cookie-Hinweise, Feature-SchalterDie deines Builders, in jeder App, die er veröffentlichtNichts zu tun
Deine eigenen öffentlichen Daten: eine Preisliste, ein ArtikelDeine, und zum Lesen gedachtNichts zu tun
Zeilen über Personen: Namen, E-Mails, Bestellungen, NachrichtenDeine, und erreichbar für jeden mit der AdresseHeute schließen

Die erste Zeile ist ein Schwarzes Brett, das jemand anderes an die Wand geschraubt hat. Die zweite ist ein Schwarzes Brett, das du absichtlich aufgehängt hast. Die dritte ist der Aktenschrank, und er steht offen, solange es die Adresse gibt.

So prüfst du, was deine Endpunkte zurückgeben

Öffne jede abgemeldet und lies, was ankommt. Das Lesen dauert länger, als der Befund vermuten lässt, und es ist der einzige Schritt, der die Frage beantwortet.

Finde die Adressen. Öffne deine Live-App, drücke F12 für die Entwicklerwerkzeuge des Browsers, klicke auf Netzwerk und lade die Seite neu. Klicke die Anfragen durch, die als JSON zurückkommen. Das sind die Adressen, von denen deine App Daten holt, und die Liste im Bericht ist eine Teilmenge davon.

Frage jede als Fremder. Kopiere eine URL, öffne ein privates Browserfenster, damit du abgemeldet bist, und füge sie ein. Was du siehst, ist genau das, was jeder im Internet an dieser Adresse sieht.

Lies die ersten Zeilen. Drei Dinge können zurückkommen:

  • Ein Fehler, eine Abweisung oder []. Die Adresse will einen Login, oder für einen abgemeldeten Aufrufer ist nichts darin. Weiter.
  • Einstellungen. Flags, Schalter, eine Farbe, eine Liste aktivierter Funktionen, eine Versionsnummer. Niemand wird genannt, nichts wird beschrieben. Weiter.
  • Zeilen. Eine E-Mail-Adresse, der Name einer Person, eine Bestellsumme, der Text einer Nachricht, eine Telefonnummer. Hier anhalten, das ist die eine.

Wenn du Zeilen findest, ändere eine Zahl in der URL, bevor du den Tab schließt. Eine Adresse, die bei id=1 den Datensatz einer Person herausgibt und bei id=2 den einer anderen, gibt alle heraus, und das ist gut zu wissen, bevor du entscheidest, wie dringend das ist.

Unser kostenloser Scan erledigt die ersten beiden Schritte für dich: er liest im Code deiner App die Adressen, die sie aufruft, fragt jede ohne Login und listet die auf, die mit Daten geantwortet haben. Er dauert etwa 20 Sekunden und braucht kein Konto: scanne deine App. Der dritte Schritt gehört dir, denn nur du weißt, ob ein Name in dieser Liste ein echter Kunde ist.

Die meisten, die wir fanden, gehören der Plattform

In unserem Sweep vom August 2026 hatten 3.852 der 30.926 Apps, bei denen diese Prüfung eine Antwort bekam, mindestens eine Adresse, die ohne Login antwortet. Davon waren 2.705 Base44-Apps, und bei Base44 ist es fast immer dieselbe Adresse.

Es ist /api/consent/config, die Cookie-Einstellungen der Plattform. Wir haben im September eine Stichprobe gemeldeter Base44-Apps erneut geöffnet, und die einzige Adresse, die antwortete, war diese. Sie gibt Einstellungen zurück, sie ist auf allen Apps identisch, und niemandes Daten stecken darin. Der Scanner meldet sie korrekt, und der Betreiber hat nichts zu beheben.

PlattformApps mit dem BefundApps, bei denen die Prüfung antwortete
Base442.7055.419
Eigene Domains82955
Bolt41.120
Lovable818.518
v001.786

Eine Plattform fehlt in dieser Tabelle mit Absicht. Auf Replit entfällt ein großer Block der übrigen Befunde, und niemand hat davon eine Stichprobe erneut geöffnet, so wie wir es bei Base44 getan haben, also wissen wir noch nicht, wessen Adressen das sind. Eine Zahl als Problem des Betreibers zu veröffentlichen, bevor jemand hingesehen hat, ist genau der Weg, auf dem eine Plattform-Einstellung von Base44 zur Statistik über nachlässige Betreiber wird. Jede Zahl hier oben stammt aus unserem Scan von 30.998 live geschalteten Vibe-Coding-Apps, der zu jeder die Basis mitveröffentlicht, über der sie gemessen wurde.

Lies die beiden Enden dieser Tabelle zusammen, denn die Spreizung ist der nützliche Teil. Bei Lovable sind es 8 Apps von 18.518, bei v0 überhaupt keine. Diese Apps haben keinen kleinen eigenen Server: die Seite spricht direkt aus dem Browser mit Supabase, es gibt also nirgends ein /api/… von dir, das diese Prüfung finden könnte. Was die Daten in so einer App schützt, sind die Regeln auf jeder Tabelle, und das ist ein anderer Befund mit einer eigenen Art, schiefzugehen.

Wann das JSON Einstellungen sind und wann Personen

Eine Frage entscheidet: beschreibt irgendetwas in der Antwort eine Person?

Einstellungen beschreiben deine App. Ein Flag dafür, ob der Dunkelmodus an ist, die Liste der Währungen, die du annimmst, die Version von irgendetwas, der Text für ein Cookie-Banner. Das zu veröffentlichen verrät, wie deine App konfiguriert ist, und deine App läuft ohnehin vor aller Augen.

Zeilen beschreiben deine Nutzer. Ein Name, eine E-Mail-Adresse, was jemand gekauft hat, was er geschrieben hat, wo er wohnt. Das ist kein Satz über eine Konfiguration, das ist der Inhalt des Aktenschranks, und es zählt deshalb, weil es so alltäglich ist, ihn mitzunehmen. Es gibt keinen Einbruch. Die Adresse ist eine Textzeile, die in jedem Browser funktioniert, also wird sie in einen Gruppenchat kopiert, in jemandes Notizen gespeichert und von einem Skript, das niemand beobachtet, im Takt abgerufen.

Beides ist eine 200 und ein Block JSON, und mehr sieht die Prüfung nicht. Der Unterschied dazwischen ist die ganze Frage, und beantworten kannst du sie durch Lesen.

Einen schließen, und warum Umbenennen ihn nicht schließt

Verlange an der Adresse einen Login, und antworte einem Aufrufer, der keinen hat, mit einer 401.

Das ist die ganze Behebung, und sie gehört an die Adresse und nirgendwo sonst. Drei Dinge, die wie die Behebung aussehen und keine sind:

  • Den Pfad umbenennen oder ihn aus deinem Code nehmen. Bei diesem ist Vorsicht geboten, weil der Befund verschwindet. Wer die URL schon hatte, hat sie weiterhin, sie steht in Browser-Verläufen und Server-Logs, und der Aktenschrank ist immer noch offen, nur ohne Schild an der Front.
  • Deinen CORS-Header verengen. Der regelt, welche anderen Websites eine Antwort im Browser von jemandem lesen dürfen, und sonst schaut ihn nichts an. Ein Skript oder ein Terminal liest dieselbe Adresse so oder so, weshalb der Wildcard-Befund eine andere Frage ist.
  • In der App filtern. Wenn die Seite die Zeilen versteckt, die sie nicht zeigen soll, schickt die Adresse sie trotzdem. Gefiltert werden muss, bevor die Antwort hinausgeht.

Es gibt eine vierte Möglichkeit, und auf einer öffentlichen Seite ist sie oft die beste: den Endpunkt abschaffen. Wenn die Daten ohnehin nur für deine eigene Seite sind, kann die Seite sie beim Bauen bekommen, und die Adresse verschwindet dabei aus deinem Code.

Wie die Behebung im Code aussieht

Zwei Dinge, im Handler, der die Adresse beantwortet: herausfinden, wer fragt, und aufhören, wenn die Antwort niemand lautet.

Vielleicht tippst du das nie selbst, und das ist in Ordnung. Lies es trotzdem, denn daran prüfst du, ob dein Builder wirklich getan hat, worum du ihn gebeten hast. Das hier ist die Form davor, und das ist, was der Scan gefunden hat:

app.get('/api/orders', async (req, res) => {
  const orders = await db.orders.findMany()
  res.json(orders)
})

Und danach:

app.get('/api/orders', async (req, res) => {
  const user = await getUserFromRequest(req)
  if (!user) {
    return res.status(401).json({ error: 'Not signed in' })
  }

  const orders = await db.orders.findMany({ where: { userId: user.id } })
  res.json(orders)
})

Zwei Dinge haben sich geändert, und beide zählen. Die 401 ist die Tür. Das where ist der Grund, warum sich die Tür lohnt: ohne es kann ein angemeldeter Besucher die Bestellungen aller anderen Kunden lesen, was ein kleineres Publikum für dieselben Daten ist und immer noch das falsche.

Bei Supabase Edge Functions lies erst die Konfiguration, bevor du irgendetwas schreibst. Edge Functions prüfen das Token des Aufrufers von sich aus: die Konfigurationsreferenz von Supabase setzt verify_jwt standardmäßig auf true, eine Funktion, die Fremden antwortet, ist also eine, bei der das jemand abgeschaltet hat, entweder mit einer Zeile in supabase/config.toml oder beim Deployen mit --no-verify-jwt:

[functions.orders]
verify_jwt = false

Wenn deine Funktion privat sein soll, lösche diese zwei Zeilen und deploye neu. Lass sie nur dort stehen, wo der Endpunkt wirklich jedem antworten muss, und das ist der Fall, den die Dokumentation von Supabase selbst als Beispiel nimmt: ein Zahlungs-Webhook. Innerhalb der Funktion gibt dir das aktuelle SDK einen Client, der bereits auf die Person zugeschnitten ist, die aufgerufen hat:

import { withSupabase } from 'npm:@supabase/server'

export default {
  fetch: withSupabase({ auth: 'user' }, async (_req, ctx) => {
    const { data } = await ctx.supabase.from('orders').select()
    return Response.json(data)
  }),
}

ctx.supabase läuft als der Aufrufer, deine Row-Level-Security-Regeln entscheiden also, was zurückkommt, und das ist derselbe Schutz, auf den sich der Rest deiner App verlässt, sowie die Sache, die man getrennt prüfen sollte.

Der Prompt zum Einfügen in deinen Builder

Wenn du in Lovable, Bolt, Cursor, Replit oder v0 baust, gib ihm das hier. Lass es auf Englisch, in welcher Sprache du das hier auch liest, denn in dieser Sprache denken diese Werkzeuge, und ersetze die beiden Teile in Klammern durch das, was dein eigener Bericht sagt:

A security scan of [my-app.com] found these API endpoints answer
without any authentication: [/api/orders, /api/users].

Please look at each one and decide whether it is meant to be public.
For the ones that are not, require a valid session or API token and
return 401 otherwise. Make sure each one returns only the rows that
belong to the person asking, not the whole table filtered in the UI.
For any that must stay open, make sure they return only non-sensitive
data and add rate limiting so they cannot be abused.

Tell me what you changed for each endpoint, and do not rename or
remove the paths.

Der letzte Satz steht dort mit Absicht. Wird ein Modell gebeten, einen Befund verschwinden zu lassen, nimmt es manchmal den kürzesten Weg und verschiebt die Adresse, und das ist das eine Ergebnis, das beim erneuten Scan wie Erfolg aussieht und nichts ändert.

Was du jetzt tun solltest

Was zu tun ist

  • Öffne jede gemeldete Adresse in einem privaten Browserfenster und lies, was zurückkommt. Das ist der Schritt, den der Bericht dir nicht abnehmen kann.
  • Sind es Einstellungen und ist die Adresse eine, die du nie geschrieben hast, gehört sie deinem Builder und es gibt nichts zu beheben. Prüfe den Pfad in der Doku deiner Plattform, bevor du Geld dafür ausgibst.
  • Enthält die Antwort einen Namen, eine E-Mail oder eine Bestellung, verlange an dieser Adresse noch heute einen Login und gib jedem ohne Login eine 401 zurück.
  • Benenne den Pfad nicht um und lösche den Verweis darauf nicht. Der Befund verschwindet, und die Adresse antwortet weiter.
  • Ändere in deiner eigenen App eine ID in der URL. Eine Adresse, die einem Fremden einen Datensatz gibt, gibt ihm meist alle.

Die Adressen auf dieser Liste hat jemand geschrieben, der damals wusste, was dahinter lag, und was sich ändert, ist das, was dahinter liegt. Reeve Care führt diesen Scan nach einem Zeitplan erneut gegen deine Live-App aus und schickt dir eine E-Mail, wenn ein Ergebnis schlechter wird, und genau diesen Fall kann der abgemeldete Durchgang oben nicht fangen: was Care überwacht und was es kostet.

Wenn du lieber alles in einem Durchgang abarbeitest: die Sicherheits-Checkliste in 10 Minuten deckt das hier neben allem anderen ab, was eine frisch gestartete App gern offen lässt, und welche Schlüssel im Frontend sicher sind ist der andere Befund, mit dem Leute meistens ankommen.

FAQ

Was bedeutet "offener API-Endpunkt" in meinem Bericht?

Es bedeutet, dass wir den Code gelesen haben, den deine App an einen Browser schickt, darin eine Adresse gefunden haben, von der deine App Daten holt, und diese Adresse ohne Konto und ohne Login nach Daten gefragt haben. Sie hat geantwortet, und die Antwort war JSON. Das ist der ganze Befund. Er hält fest, was passiert ist, und urteilt nicht darüber, ob die Daten privat waren, denn das kann nichts außerhalb deiner App wissen.

Ist jeder offene API-Endpunkt ein Sicherheitsproblem?

Nein. Viele Adressen sollen jedem antworten: eine veröffentlichte Preisliste, ein Artikel, eine Liste der aktivierten Funktionen. Das Problem ist die Adresse, die Datensätze von Personen herausgibt, denn das tut sie für jeden, der die URL hat. Lies, was zurückkam, und frage dich, ob irgendetwas davon eine Person beschreibt.

Mein Bericht meldet einen Endpunkt, den ich nie geschrieben habe. Was ist das?

Meist einer deines Builders. Plattformen, die deiner App einen kleinen eigenen Server geben, legen auch ein paar eigene Adressen hinein, und die stecken im Code jeder App auf dieser Plattform. Das klarste Beispiel, das wir gemessen haben, sind die Cookie-Einstellungen von Base44 unter /api/consent/config, auf die 2.705 der 3.852 Befunde unseres Sweeps vom August 2026 entfallen. Sie geben Einstellungen zurück, sie sind auf jeder Base44-App gleich, und nichts darin gehört dir.

Wie prüfe ich, was die Endpunkte meiner App zurückgeben?

Öffne deine Live-App, drücke F12, klicke auf Netzwerk und lade neu. Die Anfragen, die als JSON zurückkommen, sind die Adressen, von denen deine App Daten holt. Kopiere jede URL, öffne ein privates Browserfenster, damit du abgemeldet bist, und füge sie einzeln ein. Lies, was ankommt. Ein Fehler oder eine leere Liste ist in Ordnung. Zeilen mit Namen, E-Mail-Adressen oder Bestellsummen kann jeder lesen, der diese Adresse hat.

Reicht es, den Pfad zu verstecken?

Nein. Die Adresse umzubenennen oder den Verweis darauf aus deinem Code zu nehmen, hält einen Scanner vom Finden ab und lässt sie genauso antworten wie vorher. Wer die URL schon hatte, hat sie weiterhin, und sie bleibt in Browser-Verläufen, Server-Logs und allem, was deine Seite gecrawlt hat. Behoben wird es an der Adresse: einen Login verlangen und einem Fremden mit einer 401 antworten.

Geschrieben von

Vlad Tkachenko

Gründer, Reeve

Ich schaue mir den ganzen Tag Apps an, die mit Lovable, Bolt, v0, Cursor und Replit gebaut wurden, und die kurze Liste von Fehlern, die darin immer wieder auftauchen.

Mehr über den Autor

Weiterlesen

Alle Artikel

Unsicher, wie es um deine eigene App steht?

Starte einen kostenlosen Scan und erhalte in rund 20 Sekunden eine verständliche Note von A bis F. Ohne Konto, ohne Karte.

App kostenlos scannen

Automatisierte externe Prüfung, kein vollständiges Audit. Das Fehlen von Funden ist keine Garantie für Sicherheit.