Stripe-Webhook-Sicherheit
Dieser Skill führt den Stripe-Teil des vibe-preflight-Scanners aus (scripts/preflight.mjs neben dieser Datei,
Node 20+, keine Abhängigkeiten, kein Netzwerk). Er findet Webhook-Handler in Next.js-Route-Handlern, pages/api,
Express und Supabase Edge Functions und überprüft zwei Dinge im Code. Der Rest dieser Seite ist eine Überprüfung, die Sie durch
Lesen des Handlers durchführen, da ein Pattern-Scan es nicht beweisen kann.
1. Scan ausführen
Aus dem Projektstammverzeichnis:
node ${CLAUDE_SKILL_DIR}/scripts/preflight.mjs . --only stripe --fail-on warning --format json
Claude Code füllt ${CLAUDE_SKILL_DIR} aus. Andere Agenten: Verwenden Sie den Ordner dieses Skills, z. B.
.agents/skills/stripe-webhook-security oder .claude/skills/stripe-webhook-security.
| Regel | Stufe | Bedeutung |
|---|---|---|
stripe-webhook-unverified | Fehler | Der Handler reagiert auf Stripe-Ereignisse und ruft nie constructEvent / constructEventAsync auf. Jeder, der die URL findet, kann ein gefälschtes checkout.session.completed POSTen. |
stripe-webhook-parsed-body | Warnung | Die Signatur wird geprüft, aber gegen einen bereits geparsten Body. Stripe signiert die exakten Bytes, die es gesendet hat, daher schlagen echte Ereignisse mit 400 fehl und jemand löscht irgendwann die Überprüfung. |
--only stripe findet einen Handler anhand von Stripe-Ereignisnamen, einem Lesen des stripe-signature-Headers oder eines Webhook-
Pfads in einer Datei, die Stripe erwähnt. Wenn der Scan 0 Fehler meldet, der Benutzer aber weiß, dass er einen Webhook hat, suchen Sie
selbst nach dem Handler und führen Sie trotzdem die untenstehende Überprüfung durch.
2. Verifizierung reparieren
Route-Handler (Next.js App Router, Remix, Hono und andere fetch-ähnliche Handler):
export async function POST(request: Request) {
const body = await request.text(); // roher Body, niemals request.json()
const signature = request.headers.get("stripe-signature");
if (!signature) return new Response("Fehlende Signatur", { status: 400 });
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
} catch {
return new Response("Ungültige Signatur", { status: 400 });
}
// Ereignis verarbeiten
return Response.json({ received: true });
}
Express: Registrieren Sie die Webhook-Route mit express.raw({ type: "application/json" }) vor
app.use(express.json()) und übergeben Sie req.body (dann ein Buffer) an constructEvent.
Next.js pages/api: export const config = { api: { bodyParser: false } }; und lesen Sie die rohe Anfrage in einen
Buffer, bevor Sie verifizieren.
Supabase Edge Functions und andere Deno-/Edge-Laufzeiten:
await stripe.webhooks.constructEventAsync(body, signature, secret, undefined, Stripe.createSubtleCryptoProvider()).
3. Überprüfen Sie den Rest, indem Sie den Handler lesen
Der Scanner überprüft diese Punkte nicht. Sehen Sie sich den Code an und teilen Sie dem Benutzer mit, was Sie zu jedem Punkt gefunden haben.
- Secret.
STRIPE_WEBHOOK_SECRET(whsec_...) kommt aus der Server-Umgebung, niemals aus einerNEXT_PUBLIC_/VITE_-Variable oder dem Quellcode. Der Testmodus und der Live-Modus haben unterschiedliche Secrets, ebenso wie jeder Endpunkt und diestripe listen-CLI. - Wiedergabe.
constructEventlehnt eine Signatur ab, die älter als seine Toleranz ist (300 Sekunden Standardwert). Lassen Sie das Toleranzargument weg oder halten Sie es klein. Fangen Sie niemals den Fehler ab und machen Sie weiter. - Idempotenz. Stripe kann dasselbe Ereignis mehr als einmal zustellen. Machen Sie entweder den Schreibvorgang von Natur aus
wiederholbar (ein Upsert, der nach der Stripe-ID schlüsselt, setzen Sie einen Status, anstatt Guthaben hinzuzufügen) oder speichern Sie
event.idin einer Tabelle mit einer eindeutigen Einschränkung und überspringen Sie IDs, die Sie bereits gesehen haben. Achten Sie genau auf alles, was hinzufügt: Guthaben, Kontostand, Lagerbestand. - Reihenfolge. Ereignisse können in falscher Reihenfolge eintreffen. Rufen Sie für Abonnements das aktuelle Objekt von der API ab
(
stripe.subscriptions.retrieve) oder vergleichen Sie Zeitstempel, anstatt der Reihenfolge der Ereignisse zu vertrauen. - Vertrauen. Nehmen Sie Benutzer und Plan aus dem verifizierten Ereignis (
client_reference_id,metadata, die Kunden-ID, die Sie beim Checkout gespeichert haben), niemals aus einem Query-String oder einer zweiten nicht signierten Anfrage. Der Weiterleitung auf die Erfolgsseite beweist nichts: Gewähren Sie Zugriff über den Webhook. - Antwort. Geben Sie schnell 2xx zurück, sobald das Ereignis gespeichert oder verarbeitet ist. Langsame Arbeit geht an eine Warteschlange. Geben Sie 400 nur für eine ungültige Signatur zurück, damit Stripe-Wiederholungsversuche sinnvoll bleiben.
- Ereignisliste. Abonnieren Sie den Endpunkt nur für die Ereignisse, die der Code verarbeitet, und ignorieren Sie andere Typen ohne Fehler.
4. Bericht
Listen Sie die Scanner-Ergebnisse auf, dann eine Zeile pro Überprüfungspunkt: in Ordnung, behoben oder eine Frage an den Benutzer (z. B.
"Guthaben werden bei jeder Zustellung hinzugefügt: Soll ein wiederholtes Ereignis sie erneut hinzufügen?"). Führen Sie den Scan nach der
Korrektur erneut aus und zeigen Sie seine letzte Zeile. Um real zu testen, schlagen Sie stripe listen --forward-to localhost:3000/api/webhooks und stripe trigger checkout.session.completed mit der Stripe CLI vor.
Ein bestätigter Fehlalarm kann mit // vibe-preflight-ignore <Regel-ID> in der Zeile oder der
Zeile darüber mit dem Grund im Kommentar unterdrückt werden.