Ein versehentlich in ein öffentliches Repository gepushter API-Schlüssel, ein im Code fest verdrahtetes Datenbankpasswort, eine „öffentliche“ Variable, die im an den Browser ausgelieferten JavaScript-Bundle landet: Die meisten Secret-Lecks stammen nicht aus einem raffinierten Angriff, sondern aus falsch abgelegter Konfiguration. Konfiguration vom Code zu trennen bleibt einer der lohnendsten Handgriffe der Webentwicklung — und einer der am schlechtesten unterstützten in Projekten, die schnell wachsen.
Warum Konfiguration außerhalb des Codes gehört
Das Prinzip ist alt und passt in einen Satz: Der Code beschreibt, was die Anwendung tut, die Konfiguration beschreibt, wo und wie sie läuft. Ein und dieselbe Codebasis soll lokal, in der Staging-Umgebung und in Produktion starten, ohne dass sich eine einzige Zeile ändert — nur die Umgebungswerte unterscheiden sich. Das ist die von der Twelve-Factor-App-Methodik formalisierte Empfehlung, und sie hat eine direkte Sicherheitsfolge: Was in der Umgebung lebt, lebt nicht in der Git-Historie.
Die nützliche Unterscheidung ist nicht „geheim“ gegen „nicht geheim“, sondern „was sich je Umgebung ändert“. Eine Staging-API-URL ist nicht vertraulich, gehört aber genauso in die Konfiguration wie ein Zugriffstoken. Das Härten eines Servers beginnt mit demselben Reflex: keine Zugangsdaten im Klartext in einer versionierten Datei.
Die .env-Datei und die .gitignore-Regel
Die vorherrschende Konvention ist eine .env-Datei im Projektstamm: eine Liste von SCHLÜSSEL=Wert-Paaren, eines pro Zeile. Ihre Stärke ist die Einfachheit; ihre Falle ist, dass ein versehentlicher Commit trivial ist. Die erste Zeile, die man in einem Projekt schreibt, ist keine Variable, sondern der Ausschluss der Datei:
# .gitignore — à faire AVANT le premier commit
.env
.env.*
!.env.exampleDie tatsächlich versionierte Datei ist eine .env.example, die die erwarteten Schlüssel ohne ihre Werte dokumentiert. Ein neues Teammitglied kopiert sie nach .env und füllt sie aus. Eine typische Fassung sieht so aus:
# .env.example — versionné, sans secrets
APP_ENV=local
APP_DEBUG=true
DATABASE_URL=
STRIPE_SECRET_KEY=
MAIL_DSN=Das Präfix, das an den Browser ausliefert. Front-End-Build-Werkzeuge (Vite, Next.js) spielen in den Client-Code nur Variablen mit einem vereinbarten Präfix ein: VITE_ bei Vite, NEXT_PUBLIC_ bei Next.js. Alles mit diesem Präfix ist öffentlich: Es landet lesbar im Bundle, das jede Besucherin und jeder Besucher herunterlädt. Ein geheimer Schlüssel darf dieses Präfix niemals tragen, sonst wird er der ganzen Welt ausgeliefert.
Variablen je nach Stack laden
Jedes Ökosystem hat seine eigene Art, die Umgebung zu lesen. Die drei im Web gängigsten ähneln sich in der Absicht, unterscheiden sich aber im Detail.
Auf der PHP-Seite liest Laravel die .env beim Start automatisch und stellt die Werte über den Helfer env() bereit. Ein Punkt verdient Aufmerksamkeit: Sobald die Konfiguration in Produktion gecacht ist (php artisan config:cache), liefert env() außerhalb der Konfigurationsdateien nichts mehr. Der Wert muss daher überall sonst in der Anwendung über config() gelesen werden, nicht über env():
// config/services.php
return [
'stripe' => [
'secret' => env('STRIPE_SECRET_KEY'),
],
];
// Dans le code applicatif : config(), jamais env()
$key = config('services.stripe.secret');Auf der Node.js-Seite braucht das Lesen einer .env-Datei keine externe Abhängigkeit mehr: Das native Flag --env-file genügt, und die Werte landen in process.env. Derselbe Mechanismus trägt bis in ein Produktions-Deployment.
# Chargement natif, sans paquet supplémentaire
node --env-file=.env server.js// server.js — les valeurs sont dans process.env
const dsn = process.env.DATABASE_URL;
if (!dsn) {
throw new Error("DATABASE_URL manquante : vérifier le fichier .env");
}Auf der Front-End-Build-Seite stellt Vite dem Client nur die präfixierte Teilmenge bereit und macht sie über import.meta.env zugänglich. Die Grenze ist klar: Der Server sieht alles, der Browser nur das öffentliche Präfix.
// Accessible dans le navigateur — donc PUBLIC
const apiBase = import.meta.env.VITE_API_URL;Die Speichermethoden vergleichen
Die .env-Datei deckt den Entwicklungsrechner ab. Ihre Grenzen zeigt sie, sobald Secrets im Team geteilt oder regelmäßig rotiert werden müssen. Die folgende Tabelle ordnet jeden Ansatz ein.
| Methode | Geltungsbereich | Sensible Secrets | Teilen im Team |
|---|---|---|---|
.env-Datei | Lokaler Rechner, ein Projekt | Vertretbar, wenn außerhalb des Repos | Manuell, fragil |
| System- / Shell-Variablen | Rechner oder Sitzung | Ja | Keines |
direnv (.envrc-Datei) | Pro Verzeichnis, automatisch | Wie .env | Manuell |
| Secret-Manager (Vault, 1Password, SOPS) | Team, mehrere Projekte | Ja, verschlüsselt und auditiert | Zentral, nachvollziehbar |
direnv: automatisch laden und entladen
Variablen beim Projektwechsel von Hand neu zu exportieren ist eine Fehlerquelle. Das Werkzeug direnv löst das, indem es die Variablen eines Verzeichnisses lädt, sobald das Terminal es betritt, und sie beim Verlassen wieder entlädt. Die .envrc-Datei kann sogar an die bereits vorhandene .env delegieren:
# .envrc — chargé automatiquement à l'entrée du dossier
dotenv .env
export PATH="$PWD/bin:$PATH"
# Autorisation explicite (sécurité de direnv) :
# direnv allowDieser Ansatz passt gut zu einem Arbeitsplatz, der bereits mit gut gewählten Kommandozeilen-Werkzeugen ausgestattet ist: Secrets folgen dem Kontext, ohne dass eine Variable von einem Projekt ins nächste mitwandert.
Ein Secret, das nie in die Git-Historie gelangt ist, muss auch nie in Panik widerrufen werden.
Rotation, Teilen und Produktion
Drei Reflexe zeichnen ein reifes Projekt aus. Erstens die Rotation: Ein kompromittierter — oder schlicht alter — Schlüssel wird ersetzt, und die Anwendung muss diesen Wechsel ohne schweres Redeploy verkraften. Zweitens das Teilen: Ab zwei, drei Personen wird der Austausch von .env-Dateien per Chat unbeherrschbar und gefährlich — ein Secret-Manager zentralisiert den Zugriff und hält fest, wer was gelesen hat. Drittens die Produktion: Dort spielt die Hosting-Plattform oder der Orchestrator die Werte ein, nie eine committete Datei, und das Prinzip der geringsten Rechte gilt Schlüssel für Schlüssel.
Ein Leck verdient ein im Voraus geschriebenes Verfahren: den offengelegten Schlüssel beim Anbieter widerrufen, einen neuen erzeugen, den Wert aus der Historie tilgen, falls er hineingelangt ist, und die Zugriffsprotokolle prüfen. Ein in einem öffentlichen Repository veröffentlichter Schlüssel muss binnen einer Minute als kompromittiert gelten, selbst wenn das Repo sofort wieder auf privat gestellt wird: Indexierungs-Bots durchforsten Commits ununterbrochen.
Was man mitnehmen sollte
Secret-Verwaltung braucht für den Anfang kein exotisches Werkzeug: eine zuerst geschriebene .gitignore, eine versionierte .env.example und ein Auslesen durch die dafür vorgesehene Schicht jedes Frameworks decken die große Mehrheit der Projekte ab. Die Ausbaustufe — direnv, ein Secret-Manager, Dateiverschlüsselung — kommt hinzu, wenn das Team wächst oder die Secrets sich häufen. Der einzige nicht verhandelbare Schritt ist der erste: nichts Sensibles in der Git-Historie.
In meinen eigenen Projekten ist die Regel, die ich zuerst durchsetze, nicht technisch, sondern rituell: Die .gitignore und die .env.example werden vor der ersten Zeile Geschäftslogik geschrieben. Ich habe zu viele Stripe-Schlüssel und SMTP-Passwörter lecken sehen, weil die .env zu Projektbeginn „nur einmal“ committet wurde, als niemand hinsah. Die systematische Rotation nach dem Ausscheiden aus dem Team hat mir ebenfalls mehr als eine böse Überraschung erspart. — Simon Janvier
Zum Weiterlesen: der Config-Faktor der Twelve-Factor-App-Methodik, die grundlegende Referenz zur Trennung von Code und Konfiguration.
