Lektion 3.4

Secrets und Umgebung: .env, .gitignore, Deploy Keys und Verschlüsselung

Secrets sicher über Dev und Prod handhaben: env-Dateien, gitignore, von einem geleakten Secret erholen, Deploy Keys und gespeicherte Nutzer-Keys verschlüsseln

26 minDer moderne App-Stack - Auth, Daten und PaymentsVerfügbar

Was du lernst

  • Was eine .env-Datei ist, warum .gitignore nicht verhandelbar ist und separate Keys pro Umgebung
  • Was zu tun ist, wenn du ein Secret bereits gepusht hast: rotier es, denn den Commit zu löschen reicht nicht
  • Convex-Deploy-Keys und das Verschlüsseln von Nutzer-API-Keys im Ruhezustand statt als Klartext

Überblick

Du jonglierst jetzt Keys für Clerk, Convex, bald Stripe und wahrscheinlich einen KI-Provider. Jeder ist ein Schlüssel zu etwas Wertvollem - deinen Diensten, deinem Geld, den Daten deiner Kunden. Sie gut zu handhaben ist nicht optional, sobald echtes Geld und echte Nutzer im Spiel sind. Diese Lektion ist die rigorose Version der Secrets-Gewohnheit, die du in Kurs 1 begonnen hast: wo Secrets leben, wie du sie für immer aus Git heraushältst, die Notfallprozedur, wenn eines entkommt, wie Deploy Keys deinem Hosting Zugang geben, ohne deine Master-Credentials offenzulegen, und warum jeder Key, den deine Nutzer dir geben, verschlüsselt werden muss, nicht als lesbarer Text gespeichert.

Was du lernst

Du lernst, was eine env-Datei ist und warum es sie gibt, wie du .gitignore nutzt, sodass ein Secret nie committet werden kann, warum jede Umgebung ihre eigenen Keys bekommt, die genauen Wiederherstellungsschritte, wenn du ein Secret bereits gepusht hast, wie Convex-Deploy-Keys funktionieren und wohin sie gehören und wie du von Nutzern gelieferte API-Keys im Ruhezustand verschlüsselst, sodass ein Datenbank-Leak Angreifern nicht die Credentials deiner Kunden in die Hand gibt.

Voraussetzungen

Die Secrets-Basics aus Kurs 1 (Keys in .env, .env in .gitignore) und eine Multi-Service-App aus dem früheren Teil dieses Kurses, denn das Risiko wächst mit jeder Integration, die du hinzufügst. Die Grundlagen-Seite dazu, was eine env-Datei ist, deckt die absoluten Grundlagen ab, falls du sie ausbuchstabiert haben willst, bevor du tiefer gehst.

Das Problem

Geleakte Secrets sind eine der häufigsten und teuersten Einsteiger-Katastrophen, und sie passieren leise. Du fügst einen Key in eine Datei ein, um etwas zu testen, committest ihn gedankenlos, pushst zu GitHub, und jetzt ist dieser Key für immer in deiner Repository-Historie - lesbar für jeden, der das Repo sehen kann, und binnen Minuten von Bots gescrapt, die öffentliches GitHub genau danach durchsuchen. Leute mit einem geleakten Stripe- oder Cloud-Key sind zu Tausenden Dollar betrügerischer Abbuchungen aufgewacht. Und die API-Keys deiner Nutzer als Klartext zu speichern heisst, dass ein einziger Datenbank-Breach alle Kunden-Credentials auf einmal offenlegt. Nichts davon braucht Pech. Es braucht einen unachtsamen Commit. Diese Lektion macht Unachtsamkeit strukturell schwer.

Was eine .env-Datei ist und gitignore richtig gemacht

Eine .env-Datei ist eine schlichte Textdatei, die deine Secrets als KEY=value-Paare hält, getrennt von deinem Code, sodass der Code sie zur Laufzeit lesen kann, ohne dass die Werte in committeten Dateien fest eingebaut sind. Der ganze Sinn ist Trennung: der Code sagt "lies den Stripe-Key aus der Umgebung", und der eigentliche Key sitzt in .env, das nie deinen Rechner verlässt. Was das sicher macht, ist .gitignore - eine Datei, die auflistet, was Git nie tracken darf. Deine .env und all ihre Varianten gehören dorthin, immer, in jedes Projekt, ohne Ausnahme. Mach das einmal pro Projekt richtig, und ein Secret kann physisch nicht committet werden.

# .env.local - deine echten Secrets. NIE committen.
CLERK_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
CONVEX_DEPLOY_KEY=prod:your-deployment|xxxxxxxxxxxxxxxxxxxx
ENCRYPTION_KEY=base64-32-byte-random-value-here
Eine echte .env-Datei: ein Secret pro Zeile, nie committet.
# .gitignore - die nicht verhandelbaren Zeilen für jedes Projekt
node_modules
.env
.env.local
.env.*.local
.DS_Store
dist
Diese .gitignore-Zeilen verhindern, dass je eine .env-Datei in Git gelangt.

Eine nützliche Gewohnheit: committe eine .env.example-Datei (mit den Keys, aber leeren oder Fake-Werten), damit jeder, der das Projekt einrichtet, weiss, welche Variablen nötig sind, ohne je ein echtes Secret zu committen. Die Beispiel-Datei ist genau deshalb sicher zu committen, weil sie keine echten Werte enthält.

Separate Keys pro Umgebung

Development und Produktion müssen verschiedene Keys nutzen. Du hast das bei Clerk gesehen (pk_test versus pk_live), und Stripe macht dasselbe (Test- versus Live-Keys). Der Grund ist der Wirkungsradius: wenn ein Development-Key leakt, kann er nur Test-Daten und den Testmodus berühren, also ist der Schaden begrenzt. Ein geleakter Produktions-Key berührt echte Kunden und echtes Geld. Sie strikt getrennt zu halten heisst, dass ein Fehler beim Bauen nie Live-Daten erreichen kann, und du kannst einem Teammitglied oder einem Agent deine Development-Keys geben, ohne das Business zu riskieren. Verwende nie einen Produktions-Key in Development "um Zeit zu sparen" - genau so belastet ein Test-Skript versehentlich eine echte Karte oder löscht einen echten Nutzer.

  • Development-Keys (Testmodus): sicher beim Bauen zu nutzen, berühren nur Test-Daten.
  • Produktions-Keys (Live-Modus): echtes Geld, echte Nutzer - bewache sie und nutze sie nur in deiner deployten Umgebung.
  • Setze Produktions-Secrets in deinem Host (Vercel) und deinem Backend (Convex) Dashboard, nicht in einer committeten Datei.
  • Ein geleakter Dev-Key ist eine Unannehmlichkeit; ein geleakter Prod-Key kann eine Katastrophe sein. Halt sie auseinander.

Was zu tun ist, wenn du ein Secret GEPUSHT hast

Das ist der wichtigste Abschnitt der Lektion, denn es wird dir oder jemandem, mit dem du arbeitest, irgendwann passieren. Der Instinkt ist, die Zeile zu löschen und erneut zu committen, oder den Commit zu löschen. Das reicht nicht und ist gefährlich, denn das Secret lebt weiter in deiner Git-Historie und ist, falls das Repo je öffentlich war oder irgendwohin gepusht wurde, vielleicht schon gescrapt. Die einzige sichere Annahme ist, dass ein gepushtes Secret kompromittiert ist. Also ist der echte Fix, es zu rotieren: geh zum Dienst, widerrufe den geleakten Key und generiere einen neuen. Die Git-Historie zu säubern ist sekundär und optional, sobald der Key tot ist. Behandle Rotation als ersten und dringendsten Schritt, jedes einzelne Mal.

  • Nimm an, das Secret ist kompromittiert in dem Moment, in dem es gepusht wurde. Bots scannen öffentliches GitHub binnen Minuten.
  • ZUERST ROTIEREN: geh zum Dienst (Stripe, Clerk, Convex, dein KI-Provider), widerrufe den alten Key, erstelle einen neuen.
  • Aktualisiere den neuen Key in deiner .env und in deinen Host-/Backend-Umgebungsvariablen.
  • Erst dann, optional, säubere die Historie (Tools wie git filter-repo oder BFG) - aber ein toter Key kann dir ohnehin nicht schaden.
  • Prüf den Dienst auf unbefugte Aktivität (Abbuchungen, neue Nutzer, API-Aufrufe), während der Key offengelegt war.

Sag es deutlich: den Commit zu löschen macht das Leak des Secrets nicht rückgängig. Den Key zu rotieren schon. Wenn du dir eine Sache aus dieser Lektion merkst, merk dir, zuerst zu rotieren und danach aufzuräumen.

Convex-Deploy-Keys

Wenn dein Hosting (Vercel) deine App baut und deployt, braucht es die Erlaubnis, deine Backend-Funktionen und dein Schema zu Convex zu pushen. Es wäre falsch, deiner Build-Umgebung deinen persönlichen Login zu geben. Stattdessen gibt Convex einen Deploy Key aus: eine eingegrenzte Credential, die eine automatisierte Umgebung zu einem bestimmten Convex-Deployment deployen lässt und nichts weiter. Du generierst ihn im Convex-Dashboard für dein Produktions-Deployment und speicherst ihn dann als Umgebungsvariable in Vercel (nie im Code). Das Prinzip verallgemeinert sich: automatisierte Umgebungen bekommen enge, eingegrenzte Credentials, nicht deinen Master-Account, sodass ein geleakter Deploy Key einen begrenzten Wirkungsradius hat und rotiert werden kann, ohne etwas anderes zu berühren, das dir gehört.

# In Vercel als Umgebungsvariable setzen (NICHT in einer committeten Datei).
# Im Convex-Dashboard für dein PRODUKTIONS-Deployment generiert.
CONVEX_DEPLOY_KEY=prod:your-deployment-name|xxxxxxxxxxxxxxxxxxxxxxxx

# Dein Build-Befehl nutzt ihn dann, um das Backend während des Vercel-Builds zu deployen:
# bunx convex deploy --cmd "bun run build"
Ein Convex-Deploy-Key ist eine eingegrenzte Credential für automatisierte Deploys, in deinem Host gespeichert, nie committet.

Von Nutzern gelieferte Keys im Ruhezustand verschlüsseln

Hier ist ein Szenario, zu dem dieser Kurs direkt führt: dein Produkt lässt Nutzer ihren eigenen API-Key mitbringen (ihren OpenAI-Key, ihren Stripe-Key, ihren Key für einen Dienst, den du integrierst). Du speicherst ihn, damit du in ihrem Namen handeln kannst. Wenn du diesen Key als Klartext in deiner Datenbank speicherst, dann gibt ein einziger Breach einem Angreifer die Credentials jedes Kunden auf einen Schlag - eine Katastrophe, die aus einem Vorfall Hunderte macht. Der Fix ist, sensible Werte zu verschlüsseln, bevor sie in die Datenbank gehen, und sie nur in dem Moment zu entschlüsseln, in dem du sie nutzt. Selbst wenn jemand deine Datenbank stiehlt, sind die verschlüsselten Blobs ohne den Verschlüsselungs-Key nutzlos, der getrennt in deiner Umgebung lebt, nicht in der Datenbank. Das ist der Unterschied zwischen "wir hatten einen Breach" und "wir hatten einen Breach und haben die Keys jedes Kunden geleakt".

// Vor dem Speichern verschlüsseln, nur beim Nutzen entschlüsseln. Der ENCRYPTION_KEY lebt in
// deiner Umgebung, NICHT in der Datenbank, sodass eine gestohlene Datenbank allein nutzlos ist.
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto'

const key = Buffer.from(process.env.ENCRYPTION_KEY!, 'base64') // 32 bytes

export function encryptSecret(plain: string) {
  const iv = randomBytes(12)
  const cipher = createCipheriv('aes-256-gcm', key, iv)
  const enc = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()])
  const tag = cipher.getAuthTag()
  // iv + tag + ciphertext zusammen speichern; nichts davon ist für sich geheim.
  return Buffer.concat([iv, tag, enc]).toString('base64')
}

export function decryptSecret(stored: string) {
  const raw = Buffer.from(stored, 'base64')
  const iv = raw.subarray(0, 12)
  const tag = raw.subarray(12, 28)
  const enc = raw.subarray(28)
  const decipher = createDecipheriv('aes-256-gcm', key, iv)
  decipher.setAuthTag(tag)
  return Buffer.concat([decipher.update(enc), decipher.final()]).toString('utf8')
}
AES-256-GCM: Nutzer-Keys vor dem Speichern verschlüsseln, nur am Einsatzpunkt entschlüsseln. Der Key bleibt in der Umgebung.

Du musst keine Kryptografin sein, um einen gut getesteten Algorithmus wie AES-256-GCM über die Standard-Krypto-Library deiner Plattform zu nutzen, wie oben. Die Faustregel: erfinde nie deine eigene Verschlüsselung, nutze immer die Standard-Library und halt den Verschlüsselungs-Key in deiner Umgebung, völlig getrennt von den Daten, die er schützt.

Typische Fehler

Die schmerzhaften: eine .env committen, weil .gitignore fehlte oder falsch war; ein geleaktes Secret aus einer Datei löschen und glauben, es sei sicher, obwohl es noch in der Historie lebt und schon gescrapt ist; einen Produktions-Key in Development wiederverwenden und versehentlich Live-Daten berühren; einen Deploy Key oder eine Master-Credential in committeten Code legen; und Nutzer-API-Keys als Klartext speichern, sodass ein Breach sie alle leakt. Jeder davon wird durch dieselbe Disziplin verhindert: Secrets aus Git heraus, separate Keys pro Umgebung, beim Leak rotieren, sensible Daten im Ruhezustand verschlüsseln.

Business-ROI

Secret-Disziplin ist günstige Versicherung gegen eine Kategorie von Katastrophen, die kleine Produkte in den Ruin getrieben hat: eine ausufernde Cloud-Rechnung durch einen geleakten Key, einen Betrugsvorfall durch einen geleakten Payment-Key oder einen Breach, der Kunden-Credentials offenlegt und rechtliche Haftung unter der DSGVO und den US-Datenschutzgesetzen auslöst. Die Kosten, das richtig zu machen, sind ein paar Minuten Setup pro Projekt und eine Standard-Verschlüsselungsfunktion, die du einmal schreibst. Die Kosten, es falsch zu machen, werden in Tausenden Dollar, rechtlicher Exposition und verlorenem Vertrauen gemessen, das du vielleicht nie zurückgewinnst. Für eine Gründerin ist das eine der Gewohnheiten mit der höchsten Rendite pro Aufwand im ganzen Stack.

Checkliste

Du bist sicher genug, um Payments hinzuzufügen, wenn all das über jedes Projekt stimmt, das du shippst.

  • Jedes Projekt hat eine korrekte .gitignore und keine .env wurde je committet.
  • Development und Produktion nutzen separate Keys, mit Prod-Secrets in deinen Host- und Backend-Dashboards gesetzt.
  • Du kennst die Regel: ein gepushtes Secret wird zuerst rotiert, die Historie danach gesäubert.
  • Jeder von Nutzern gelieferte API-Key ist im Ruhezustand verschlüsselt, mit dem Verschlüsselungs-Key aus der Datenbank herausgehalten.

Ressourcen

Halt die API-Key- und Rotations-Seiten deiner Dienste als Lesezeichen, damit du einen geleakten Key in Sekunden widerrufen kannst, wenn der Moment kommt. GitHub bietet auch Secret Scanning an, das dich auf geleakte Keys aufmerksam machen kann - schalt es ein. Die Grundlagen-Seite dazu, was eine env-Datei ist, deckt die Basics ab. Mit deinem abgesicherten Stack fügen die nächsten zwei Lektionen das Geld hinzu: Stripe Checkout und Subscriptions, dann die kniffligeren Billing-Details.

Deine Aufgabe

Auditiere eines deiner echten Projekte jetzt sofort. Bestätige, dass .gitignore jede .env-Variante ausschliesst, durchsuche die Repo-Historie nach einem versehentlich committeten Key, und falls du einen findest, rotier ihn sofort. Füge dann eine .env.example mit leeren Werten hinzu, damit das Projekt seine benötigten Secrets sicher dokumentiert. Wenn deine App einen von Nutzern gelieferten Key speichert, umhülle ihn mit den encrypt/decrypt-Funktionen oben. Dieses Audit ist die Art Sache, die Gründer aufschieben, bis es zu spät ist - mach es heute.

Nächste Lektion

Dein Stack ist sicher. Jetzt lass ihn verdienen. Die nächste Lektion fügt Stripe hinzu: Hosted versus Embedded Checkout, Produkte und Preise, Subscriptions mit monatlicher und jährlicher Abrechnung und die strikte Test-versus-Produktion-Disziplin, die dich Payments bauen lässt, ohne je eine echte Abbuchung zu riskieren.

Kommentare

Kommentare werden geladen.

Kommentar schreiben
KommentareWeiter
Nächster Schritt

Bereit, KI als Workflow zu nutzen?

Starte mit dem Starter-Pfad, speichere deinen Fortschritt lokal und synchronisiere alles später kostenlos mit deinem Konto.