Scanner für offengelegte API-Keys

Alles in deinem Bundle ist öffentlich.

Gib eine URL ein und wir holen das JavaScript, das deine Seite den Browser laden lässt, und lesen es dann auf Zugangsdaten. Keine Vermutung aus dem HTML: die echten Skriptdateien, dieselben, die jeder in den DevTools öffnen kann.

Prüfe nur Seiten, die dir gehören oder die du testen darfst. Wir fordern die Seite an, wie es ein Browser täte, und lesen, was zurückkommt. Sieh dir unsere Nutzungsbedingungen.

Was hier geprüft wird, und wie eine gute Antwort aussieht

Supabase service_role-Keys

Das Schlimmste auf dieser Liste. Der service_role-Key umgeht jede Row-Level-Security-Policy, die du geschrieben hast, also kann, wer ihn hat, jede Zeile jeder Tabelle lesen, ändern und löschen, ganz gleich, als wer er angemeldet ist. Er sieht genauso aus wie der veröffentlichbare anon-Key, und deshalb landet er im Client-Code: jemand lief in einen Berechtigungsfehler, tauschte den einen Key gegen den anderen, der Fehler war weg, und so ging es live.

Supabase anon-Keys und Firebase-Konfiguration

Diese sollen öffentlich sein, und sie zu finden ist für sich noch kein Befund. Wir melden sie, weil ihr Wert vollständig von deinen Regeln abhängt: ein anon-Key mit abgeschalteter Row Level Security liest deine ganze Datenbank, und eine Firebase-Konfiguration, deren Regeln auf allow read, write: if true stehen, tut dasselbe. Der Key ist nicht die Schwachstelle. Der Key plus eine fehlende Policy schon.

Stripe-Secret-Keys

Ein sk_live_-Key im Client-Code bedeutet, dass jeder Zahlungen anlegen, Erstattungen auslösen und deine Kundenliste lesen kann. Wir markieren auch Test-Keys, mit geringerem Schweregrad: ein sk_test_ im Bundle kostet kein Geld, aber es sagt, dass der veröffentlichbare und der geheime Key einmal verwechselt wurden, und derselbe Fehler erreicht irgendwann meist die Produktion.

AWS-Zugangsschlüssel, GitHub-Tokens und private Schlüssel

Ein AWS-Schlüsselpaar in einem Bundle ist gleichzeitig eine Rechnung und ein Sicherheitsvorfall, und das sind die Zugangsdaten, hinter denen automatische Sammler am härtesten her sind. Ein GitHub-Token legt den Quellcode offen und, wenn es Schreibrechte trägt, auch die Möglichkeit zu pushen. Ein BEGIN PRIVATE KEY-Block im Client-Code hat dort überhaupt keinen legitimen Grund zu sein.

Keys von Modellanbietern

Keys von OpenAI, Anthropic und ähnlichen Anbietern im Client-Code sind der neueste Eintrag auf dieser Liste und zunehmend der häufigste, weil der Aufruf des Modells direkt aus dem Browser der kürzeste Weg zu einer funktionierenden Demo ist. Es ist zugleich ein abgerechneter Zugang, den jeder ausgeben kann. Das ist der Fehler, aus dem über Nacht eine vierstellige Rechnung wird statt eines Datenlecks.

Fest eingetragene Authorization-Tokens

Ein wörtlicher Authorization: Bearer ...-Wert, ins Bundle eingebacken, meistens nach einem Test gegen einen echten Endpoint liegen geblieben. Er gewährt, was immer das Token gewährt, jedem, solange es gültig bleibt.

Datenbank-Verbindungsstrings

Eine postgres:// oder mongodb+srv:// URL mitsamt Passwort, in einer Datei, die jeder Besucher herunterlädt. Das ist die direkteste Fassung des ganzen Problems: sie gilt für keinen bestimmten Nutzer, keine Policy steht dahinter, und wer sie kopiert, kann jede Tabelle lesen, jede Zeile ändern und alles löschen. Das Passwort geben wir nie zurück, nur den Host, damit du weißt, um welche Datenbank es geht. Tutorial-Beispiele wie postgres://user:password@localhost werden erkannt und übergangen, weshalb es diesen Alarm nicht auslöst, wenn du dein Setup auf deiner eigenen Seite dokumentierst.

Slack, SendGrid, Twilio, npm, Resend, Mapbox und Algolia

Der lange Schwanz, und der Grund, warum ein Scanner, der nur die berühmten fünf kennt, so viel übersieht. Ein Slack-Bot-Token liest deine privaten Kanäle. Ein SendGrid- oder Resend-Key verschickt Mail mit der Reputation deiner Domain im Rücken, weshalb geleakte Keys binnen Stunden zu Phishing werden. Ein npm-Publish-Token kann Code an jeden ausliefern, der dich installiert. Zwei davon sind bewusst niedriger eingestuft: eine Slack-Webhook-URL schreibt nur in einen einzigen Kanal, und eine Twilio-Konto-Kennung allein öffnet gar nichts, also wird keine von beiden als Datenleck verkleidet. Die Algolia-Prüfung ist als Frage formuliert und nicht als Urteil, denn der Such-Key und der Admin-Key sind identische Zeichenketten, und sie auseinanderzuhalten hieße, den Key zu benutzen, was ein passiver Scan nicht tut.

So behebst du es

Es gibt nur einen echten Fix, und der hat zwei Hälften: roll die Zugangsdaten, denn alles, was einen Browser erreicht hat, muss als in fremden Händen gelten, und verschieb dann den Aufruf, der sie brauchte, auf den Server. Das Rollen ist weder optional noch etwas, das man aufschiebt. Keys in öffentlichen Bundles finden automatische Scanner in Stunden.

Die Variablennamen, die leaken
# Anything with these prefixes is INLINED INTO THE BUNDLE at build
# time. Not read at runtime, not kept on the server: pasted into a
# file anyone can download. A secret here is a published secret.

NEXT_PUBLIC_*      # Next.js
VITE_*             # Vite, so also most Lovable and Bolt output
PUBLIC_*           # Astro, SvelteKit
REACT_APP_*        # Create React App
EXPO_PUBLIC_*      # Expo

# Server-only names have no prefix, and your host's secret store is
# where they belong:
SUPABASE_SERVICE_ROLE_KEY=...
STRIPE_SECRET_KEY=...
OPENAI_API_KEY=...
Den Aufruf auf den Server holen (Next.js)
// app/api/summarise/route.ts
// The key stays on the server. The browser calls YOUR route, which
// is also the only place you can enforce who is allowed to ask.
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const session = await auth();
  if (!session) return NextResponse.json({ error: "sign in" }, { status: 401 });

  const answer = await fetch("https://api.openai.com/v1/responses", {
    method: "POST",
    headers: {
      authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({ model: "gpt-5.4-mini", input: await request.text() }),
  });

  return NextResponse.json(await answer.json());
}
Den Aufruf auf den Server holen (Supabase Edge Function)
// supabase/functions/admin-task/index.ts
// service_role belongs here and nowhere else. Deno.env reads the
// function's own secrets, which are never bundled to a client.
import { createClient } from "jsr:@supabase/supabase-js";

const admin = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!,
);

Deno.serve(async (request) => {
  const token = request.headers.get("authorization");
  if (!token) return new Response("unauthorized", { status: 401 });
  // Check who is asking BEFORE using a key that ignores every policy.
  const { data: user } = await admin.auth.getUser(token.replace("Bearer ", ""));
  if (!user) return new Response("unauthorized", { status: 401 });

  return Response.json({ ok: true });
});
Rollen, der Reihe nach
1. Issue the new credential first, so nothing goes down.
2. Update your host's secret store and redeploy.
3. Revoke the old one. This is the step that actually helps, and
   the one people skip because everything already works without it.
4. Check the provider's audit log for use you cannot account for.
5. Search your git HISTORY, not just your files:
     git log -p -S 'sk_live_' -- . | head -50
   Deleting a key from a file leaves it in every clone of the repo.
Was dieses Tool nicht sehen kann

Das hier liest die Skripte, auf die deine Seite verweist, sieht also, was der Browser eines Besuchers sieht, und nicht mehr. Ein Key, der nur auf einer Route auftaucht, die wir nicht geladen haben, in einem Bundle hinter einem Login, oder in deiner Git-Historie statt in deinem aktuellen Build, ist hier unsichtbar und genauso gefährlich. Vor allem die Git-Historie ist der Ort, an dem die meisten geleakten Keys tatsächlich liegen: ein Secret aus einer Datei zu entfernen entfernt es nicht aus dem Repository, und das ist einer der ersten Orte, an denen ein Scan des Codes selbst nachsieht.

Auch den Code scannenZum Einstieg kostenlos. Repository verbinden, der erste Scan läuft in rund zwei Minuten.