Irgendwo in Ihrem Code steht ein Passwort. Vielleicht in einer .env-Datei, die “nur lokal” ist. Vielleicht in einem Slack-Chat von 2021. Vielleicht in einem GitHub-Commit, den Sie längst vergessen haben.
Das Problem: Bots haben nicht vergessen. Sie scannen GitHub 24/7 nach geleakten AWS-Keys, Stripe-Secrets und Datenbank-Credentials. Ein Treffer, und innerhalb von Minuten laufen auf Ihrem Account Crypto-Miner. Oder schlimmer.
Die gute Nachricht: Secrets richtig zu verwalten ist kein Hexenwerk. Es braucht nur ein System.
Was sind Secrets?
Secrets sind alle Daten, die Ihre Anwendung braucht, aber niemand sehen sollte:
| Typ | Beispiel | Schadenspotenzial |
|---|---|---|
| Datenbank-Credentials | postgresql://user:pass@host/db | Datenleck, Manipulation, Löschung |
| API-Keys | Stripe, OpenAI, Twilio | Kosten, Missbrauch im Namen Ihres Accounts |
| JWT-Secrets | Signing Key für Tokens | Komplette Auth-Übernahme |
| SSH-Keys | Deployment-Zugang | Server-Zugriff, Malware |
| SMTP-Credentials | Mail-Server-Zugang | Spam, Phishing unter Ihrer Domain |
Die Faustregel: Wenn es peinlich wäre, wenn es jemand sieht – es ist ein Secret.
Die fünf Todsünden
Bevor wir zu den Lösungen kommen, die Klassiker des Versagens:
1. Hardcodierte Secrets im Code
// Das hier. Nicht machen.
const stripeKey = "sk_live_abc123...";
Einmal committen, für immer in der Git-History. Auch in privaten Repos ein Risiko.
2. .env in Git committen
“Ich hab es nur in den main-Branch gepusht, das sieht doch keiner.” Doch.
3. Secrets im Slack/E-Mail teilen “Kannst du mir kurz den Datenbank-Zugang schicken?” – Jetzt liegt er für immer in einem Chat-Log.
4. Gleiche Secrets überall Dev, Staging, Prod – alles das gleiche Passwort. Wenn einer fällt, fallen alle.
5. “Das rotieren wir später” Spoiler: Später ist nie. Das Secret aus 2019 läuft immer noch.
GitHub Secrets einrichten
GitHub hat ein eingebautes Secrets-Management. Es ist nicht perfekt, aber es ist da.
Repository Secrets
Pfad: Repository → Settings → Secrets and variables → Actions → New repository secret
Das sind die Secrets, die nur dieses Repo braucht:
| Secret | Woher | Generierung |
|---|---|---|
DATABASE_URL | Ihr Datenbankprovider | postgresql://user:pass@host:5432/db |
JWT_SECRET | Selbst generieren | openssl rand -base64 32 |
STRIPE_SECRET_KEY | Stripe Dashboard | Kopieren |
SMTP_PASS | Mail-Provider | Provider-Interface |
Organization Secrets
Pfad: Organization → Settings → Secrets and variables → Actions
Für Secrets, die mehrere Repos brauchen: Docker Registry Credentials, NPM Tokens, Shared API Keys.
Environment Secrets
Pfad: Repository → Settings → Environments → New environment
Der eleganteste Weg: Unterschiedliche Secrets für development, staging, production. Das Production-Stripe-Key sollte niemand sehen, der nur am Staging arbeitet.
Secrets in GitHub Actions
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # Wichtig: Environment angeben
steps:
- uses: actions/checkout@v4
- name: Build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
run: npm run build
Was GitHub für Sie tut:
- Secrets werden nie in Logs ausgegeben (maskiert)
- Secrets werden verschlüsselt gespeichert
- Nur Workflows können sie lesen, nicht Menschen
Was GitHub nicht für Sie tut:
- Verhindern, dass Sie
echo $SECRETin einen Log schreiben - Secrets rotieren
- Prüfen, ob Ihre Secrets stark genug sind
Projektstruktur
projekt/
├── .env.example # Committen! Vorlage mit Platzhaltern
├── .env # NICHT committen! Echte Werte
├── .env.test # NICHT committen! Test-Umgebung
└── .gitignore # Muss .env enthalten
.gitignore – nicht verhandelbar
# Environment files - ALLE außer example
.env
.env.*
!.env.example
# Keys und Zertifikate
*.pem
*.key
*.p12
secrets/
# IDE-spezifische Configs (enthalten oft Secrets)
.idea/
.vscode/settings.json
.env.example – die Dokumentation
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
# Auth
JWT_SECRET=generate-with-openssl-rand-base64-32
# Stripe (use test keys in development!)
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Mail (use Mailhog or similar in development)
SMTP_HOST=localhost
SMTP_PORT=1025
SMTP_USER=
SMTP_PASS=
Tipp: Kommentare, die erklären, wie man an die echten Werte kommt. “Frag Max im Slack” ist besser als nichts. Ein Link zum Password-Manager ist besser als “Frag Max”.
Secrets generieren
JWT Secret (mindestens 32 Zeichen)
openssl rand -base64 32
# Ausgabe: etwas wie K7dF9xMn2pQr5tYw8vBcHj3kLmNq6sPu
# Oder mit Node
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
Passwort (lesbar, aber sicher)
openssl rand -base64 20 | tr -d '/+=' | head -c 20
SSH Deployment Key
# Ed25519 – moderner, kürzer, sicherer
ssh-keygen -t ed25519 -C "deploy@projekt" -f deploy_key -N ""
# Public Key auf Server
cat deploy_key.pub >> ~/.ssh/authorized_keys
# Private Key als GitHub Secret
cat deploy_key | base64 # Base64, weil Zeilenumbrüche Probleme machen
Validierung zur Laufzeit
Das Schlimmste: Die App startet, aber ein Secret fehlt. Statt sofort zu crashen, funktioniert sie 95% – bis jemand die eine Funktion aufruft, die das Secret braucht.
Lösung: Secrets beim Start validieren.
// src/config/env.ts
import { createEnv } from "@t3-oss/env-core";
import { z } from "zod";
export const env = createEnv({
server: {
NODE_ENV: z.enum(["development", "production", "test"]),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32, "JWT_SECRET muss mindestens 32 Zeichen haben"),
STRIPE_SECRET_KEY: z.string().startsWith("sk_", "Muss ein Stripe Secret Key sein"),
},
runtimeEnv: process.env,
});
Wenn jetzt ein Secret fehlt oder falsch ist, crasht die App sofort beim Start – mit einer klaren Fehlermeldung. Nicht irgendwann um 3 Uhr nachts.
Secrets in Produktion
Vercel / Railway / Fly.io
Die meisten modernen Hosting-Plattformen haben ein eingebautes Secrets-Management:
- Vercel: Project Settings → Environment Variables
- Railway: Variables Tab im Service
- Fly.io:
fly secrets set DATABASE_URL=postgres://...
Nutzen Sie es. Keine .env-Dateien auf Servern.
VPS / Eigene Server
# Systemd Unit mit Environment
[Service]
EnvironmentFile=/etc/myapp/secrets
Die /etc/myapp/secrets-Datei gehört root, ist nur für den Service lesbar (chmod 600), und wird nicht ins Backup eingeschlossen.
Docker
# docker-compose.prod.yml
services:
api:
secrets:
- db_password
- jwt_secret
secrets:
db_password:
external: true # Muss vorher mit 'docker secret create' angelegt werden
jwt_secret:
external: true
Enterprise: Secret Manager
Ab einer gewissen Größe oder bei Compliance-Anforderungen:
- HashiCorp Vault – Self-hosted, mächtig, komplex
- AWS Secrets Manager – Wenn Sie eh in AWS sind
- Google Secret Manager – Dito für GCP
- 1Password / Doppler – Entwicklerfreundliche SaaS-Lösungen
Brauchen Sie das? Wahrscheinlich nicht, wenn Sie diesen Artikel lesen. GitHub Secrets + vernünftige Hygiene reichen für die meisten Projekte.
Wenn es passiert ist
Sie haben ein Secret committet. Oder Sie vermuten es. Was jetzt?
1. Secret sofort invalidieren
Nicht morgen. Nicht nach dem Meeting. Jetzt. Neuen API-Key generieren, altes Secret deaktivieren.
2. Aus Git-History entfernen
# BFG Repo-Cleaner (schneller als git filter-branch)
bfg --delete-files .env
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push origin --force --all
3. GitHub Support kontaktieren
Cached Daten, Pull Requests, Forks – vieles bleibt auch nach Force Push. GitHub kann helfen.
4. Schadensbegrenzung
- Logs prüfen: Wurde das Secret benutzt?
- Bei Datenbank-Credentials: Zugriffslogs checken
- Bei Payment-Keys: Transaktionen prüfen
- Bei Cloud-Credentials: Nutzung und Kosten prüfen
Die harte Wahrheit: Ein einmal gepushtes Secret ist kompromittiert. Punkt. Bots finden es in Sekunden. Die History-Bereinigung ist Schadensbegrenzung, keine Lösung.
Checkliste
Bevor Sie git push ausführen:
-
git diff --stagedzeigt keine Secrets -
.envist in.gitignore -
.env.exampleist aktuell - Neue Secrets sind in GitHub dokumentiert
- CI/CD Workflow funktioniert mit den neuen Secrets
Einmal im Quartal:
- Secrets rotieren (mindestens die kritischen)
- Alte, nicht mehr genutzte Secrets löschen
-
.env.examplenoch aktuell? - Wer hat Zugriff auf welche Secrets?
Die Kurzfassung
Secrets Management ist kein Hexenwerk. Es ist Hygiene. Wie Zähneputzen – langweilig, aber die Alternative ist schmerzhaft.
Die Kurzfassung:
- Secrets nie in Git
.env.examplecommitten,.envnicht- GitHub Secrets für CI/CD
- Unterschiedliche Secrets für Dev/Staging/Prod
- Validieren beim App-Start
- Rotieren, bevor es wehtut
Das ist alles. Keine Enterprise-Software nötig, keine komplexe Infrastruktur. Nur Disziplin.
Und wenn Sie denken “bei mir ist das nicht so kritisch” – fragen Sie sich, wie Sie sich fühlen würden, wenn Ihre Datenbank morgen öffentlich wäre. Wenn die Antwort nicht “entspannt” ist, haben Sie Arbeit vor sich.
Weiterführend
- GitHub Encrypted Secrets – Offizielle Docs
- 12 Factor App: Config – Die Grundlagen
- t3-env – Typsichere Environment Variables
- BFG Repo-Cleaner – Für den Notfall