Tips & Tricks

7 Sicherheitsvorfälle mit Claude Code | Echte Unfälle und Prävention

Sieben reale Sicherheitsvorfälle mit Claude Code: .env-Lecks, Produktionsdatenbank-Löschungen, Abrechnungsexplosionen und mehr — mit Ursachenanalyse und Präventionscode.

„Claude Code ist praktisch, aber irgendwie auch unheimlich” — dieses Gefühl ist berechtigt. Mächtige Werkzeuge verursachen mächtige Unfälle.

Dieser Artikel behandelt sieben reale Sicherheitsvorfälle, die bei der Entwicklung mit Claude Code passieren können, und erklärt warum sie geschahen und wie man sie verhindert — mit konkretem Code und Konfigurationen. Lernen Sie aus den Fehlern anderer, bevor sie zu Ihren eigenen werden.

Fall 1: .env-Datei auf GitHub gepusht

Was passierte

Ein Entwickler gab Claude Code folgende Anweisung: „Ich möchte Umgebungsvariablen an CI übergeben, bitte committe auch die .env-Datei.” Claude Code führte brav git add .env && git commit aus. Minuten nach dem Push auf GitHub wurde der API-Schlüssel von einem Crawler entdeckt. Eine Slack-Benachrichtigung erschien: „Your API key has been exposed.”

Ursache

  • .env war nicht in .gitignore eingetragen
  • Claude Code führt „committe das” buchstäblich aus
  • Der Benutzer bestätigte den Bestätigungsdialog gedankenlos

Präventionscode

1. Sicherheits-Setup bei der Projekterstellung automatisieren

# scripts/init-security.sh — bei jeder Projekterstellung ausführen
#!/bin/bash
cat >> .gitignore << 'EOF'

# === Sicherheit: Niemals committen ===
.env
.env.*
.env.local
!.env.example
*.pem
*.key
*-service-account.json
credentials.json
EOF

echo "✓ Sicherheitsausschlüsse zu .gitignore hinzugefügt"
git add .gitignore && git commit -m "security: add .gitignore patterns"

2. Vor dem Commit mit einem Hook scannen

.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git add*)",
        "hooks": [{
          "type": "command",
          "command": "git diff --cached --name-only | grep -E '^\\.env' && echo '🚨 Sie sind dabei, eine .env-Datei zu stagen! Abbrechen!' && exit 1 || exit 0"
        }]
      }
    ]
  }
}

3. Wiederherstellung nach einem Push

# Schritt 1: API-Schlüssel sofort rotieren (oberste Priorität)

# Schritt 2: Vollständig aus der git-Historie entfernen
git filter-branch --force --index-filter \
  "git rm --cached --ignore-unmatch .env" \
  --prune-empty --tag-name-filter cat -- --all

# Schritt 3: Force Push zum Remote
git push origin --force --all

# Schritt 4: GitHub-Cache leeren (auch GitHub-Support kontaktieren)

Fall 2: DROP TABLE auf der Produktionsdatenbank ausgeführt

Was passierte

„Diese Tabelle wird nicht mehr gebraucht, bitte lösch sie.” Claude Code generierte und führte DROP TABLE old_users; aus. Das Problem: Es war mit der Produktions-DATABASE_URL verbunden. Das neueste Backup war drei Tage alt. Drei Tage Daten waren weg.

Ursache

  • Dieselbe .env wurde zwischen Entwicklung und Produktion geteilt
  • Claude Code kann zwischen Umgebungen nicht unterscheiden
  • Der Benutzer war auf ask eingestellt, klickte aber reflexartig auf „OK”

Präventionscode

1. .env-Dateien vollständig nach Umgebung trennen

.env.development   # ← lokale Entwicklung, Test-DB
.env.staging       # ← Staging, Produktionskopie
.env.production    # ← Produktion, manuell verwaltet, niemals teilen

2. Umgebungsprüfung in Skripte einbauen

// scripts/db-migrate.mjs
const env = process.env.APP_ENV ?? "development";
const dbUrl = process.env.DATABASE_URL ?? "";

if (env === "production") {
  const readline = require("readline").createInterface({
    input: process.stdin, output: process.stdout
  });
  await new Promise((resolve) => {
    readline.question(
      `⚠️  Verbindung zur Produktions-DB (${dbUrl.split("@")[1]}).\nMöchten Sie wirklich fortfahren? (yes eingeben): `,
      (answer) => {
        readline.close();
        if (answer !== "yes") { console.log("Abgebrochen."); process.exit(0); }
        resolve(undefined);
      }
    );
  });
}

3. Produktionsoperationen in CLAUDE.md verbieten

## 🚨 Einschränkungen für die Produktionsumgebung

Wenn DATABASE_URL `prod`, `production` oder `live` enthält:
- Niemals DROP / TRUNCATE / DELETE (ohne WHERE-Klausel) ausführen
- Vor Migrationen immer Benutzerbestätigung einholen
- Vor destruktiven Operationen Backup-Befehl präsentieren

Fall 3: Wichtige Dateien mit rm -rf gelöscht

Was passierte

„Bereinige das build/-Verzeichnis” — ein Tippfehler im Pfad führte zu rm -rf ./ und löschte das gesamte Projekt. Dateien außerhalb von git (lokale Konfiguration, nicht committete Experimentiercode) waren für immer verloren.

Ursache

  • rm -rf ist eines der gefährlichsten Befehle für Claude Code
  • Fehlende Anführungszeichen um Pfade → Fehlfunktion bei Pfaden mit Leerzeichen
  • Der Benutzer bestätigte achtlos

Präventionscode

// .claude/settings.json
{
  "permissions": {
    "deny": [
      "Bash(rm -rf /)",
      "Bash(rm -rf ~*)",
      "Bash(rm -rf .*)"
    ],
    "ask": [
      "Bash(rm -rf*)"
    ]
  }
}

Hook zum Anzeigen von zu löschenden Dateien vor der Ausführung:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(rm*)",
        "hooks": [{
          "type": "command",
          "command": "echo '⚠️ Löschbefehl erkannt. Ausführung in 5 Sekunden. Ctrl+C zum Abbrechen.' && sleep 5"
        }]
      }
    ]
  }
}

Fall 4: API-Schlüssel direkt im Prompt geschrieben und an Subagent übergeben

Was passierte

„Bitte poste auf Qiita mit QIITA_TOKEN=abc123def456” — direkt in den Prompt geschrieben und an einen Subagent delegiert. Subagents können Inhalte in Logs und Memory schreiben, und das Token landete in einer Logdatei unter .claude/.

Ursache

  • Prompts werden als Gesprächsverlauf gespeichert
  • Subagent-Prompts werden ebenso protokolliert
  • Auch in lokalen Umgebungen können andere Prozesse oder Backups Geheimnisse preisgeben

Präventionscode

Niemals Geheimnisse in Prompts schreiben — über Umgebungsvariablen übergeben

# ❌ Gefährlich
claude -p "Verwende QIITA_TOKEN=abc123 um qiita-publish.mjs auszuführen"

# ✅ Sicher: Skript liest aus process.env
# QIITA_TOKEN=abc123 in .env schreiben, dann
claude -p "Führe scripts/qiita-publish.mjs aus (Token wird automatisch aus .env gelesen)"

Gleiches Prinzip für Subagent-Anweisungen

// ❌ Gefährlich
Agent({ prompt: `Verwende API-Schlüssel ${process.env.SECRET_KEY} für...` });

// ✅ Sicher: Nur den Namen des Schlüssels übergeben, Wert im Skript lesen
Agent({ prompt: "Verwende die SECRET_KEY-Umgebungsvariable für..." });

Fall 5: Endloser API-Retry-Loop ließ die Rechnung explodieren

Was passierte

„Bei Fehler automatisch wiederholen” — ein Skript mit Fehlerbehandlung wurde generiert. Bei einem nicht lösbaren Fehler stoppten die Wiederholungsversuche nie: 3.000 Anthropic-API-Aufrufe in einer Stunde führten zu einer Rechnung von 200 $.

Ursache

  • Kein Retry-Limit gesetzt
  • Kein exponentieller Backoff — Endlosschleife im Sekundentakt
  • Kein Abrechnungsalert konfiguriert

Präventionscode

// utils/retry.ts — sicheres Retry-Utility
export async function withRetry<T>(
  fn: () => Promise<T>,
  options = { maxAttempts: 3, baseDelayMs: 1000, maxDelayMs: 30000 }
): Promise<T> {
  let lastError: Error;

  for (let attempt = 1; attempt <= options.maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastError = err as Error;
      if (attempt === options.maxAttempts) break;

      // Exponential backoff + jitter
      const delay = Math.min(
        options.baseDelayMs * Math.pow(2, attempt - 1) + Math.random() * 1000,
        options.maxDelayMs
      );
      console.warn(`Attempt ${attempt}/${options.maxAttempts} failed: ${err.message}`);
      console.warn(`Retrying in ${Math.round(delay / 1000)}s...`);
      await new Promise((r) => setTimeout(r, delay));
    }
  }

  throw new Error(`Nach ${options.maxAttempts} Versuchen fehlgeschlagen: ${lastError!.message}`);
}

In CLAUDE.md festlegen:

## Pflichtregeln für API-Aufrufe
- Maximal 3 Wiederholungsversuche
- Immer exponentiellen Backoff implementieren (1s → 2s → 4s)
- Keine Endlosschleifen erstellen: while(true) + API-Aufrufe sind verboten

Fall 6: git push --force löschte Commits eines Kollegen

Was passierte

„Überschreibe den Remote mit dem lokalen Stand” — git push --force wurde ausgeführt. Drei Commits, die ein Teammitglied gerade gepusht hatte, waren weg. Dieses Mitglied hatte keine lokale Kopie der Änderungen — der Code war dauerhaft verloren.

Ursache

  • --force wird oft ohne Verständnis der Gefahr ausgeführt
  • Claude Code führt „Remote überschreiben” buchstäblich aus
  • Der Entwickler kannte die sicherere Alternative git push --force-with-lease nicht

Präventionscode

// .claude/settings.json
{
  "permissions": {
    "deny": [
      "Bash(git push --force *master*)",
      "Bash(git push --force *main*)",
      "Bash(git push -f *master*)",
      "Bash(git push -f *main*)"
    ]
  }
}

Sichere Alternative in CLAUDE.md festlegen:

## Sichere Git-Regeln
- `git push --force` ist **verboten**
- Stattdessen `git push --force-with-lease` verwenden
  (wird automatisch abgelehnt, wenn andere Änderungen gepusht haben)
- Vor direktem Push auf main/master immer Benutzerbestätigung einholen

Fall 7: Zu privilegiertes Service-Konto griff auf alle Ressourcen zu

Was passierte

„Verwende diesen GCP-Service-Account-Schlüssel für Cloud Storage-Operationen.” Das Service-Konto hatte Owner-Berechtigungen. Claude Code verbat sich nicht nur mit Cloud Storage, sondern auch mit BigQuery, Cloud SQL und GKE-Clustern „zur Untersuchung” — was zu unerwarteten Kosten führte.

Ursache

  • Das Service-Konto hatte übermäßige Berechtigungen (Verletzung des Least-Privilege-Prinzips)
  • Claude Code neigt dazu, verfügbare Werkzeuge aktiv zu nutzen
  • Selbst „zur Untersuchung” klingt wie ein legitimer Grund für breiten Zugriff

Präventionscode

Service-Konto mit minimalen Berechtigungen erstellen:

# ❌ Vermeiden: Owner-Berechtigung
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/owner"

# ✅ Nur minimal erforderliche Berechtigungen
gcloud projects add-iam-policy-binding PROJECT_ID \
  --member="serviceAccount:[email protected]" \
  --role="roles/storage.objectAdmin"
  # ← Lesen/Schreiben auf Cloud Storage beschränkt

Zugriffsbereich explizit in CLAUDE.md definieren:

## GCP-Zugriffsbeschränkungen
Berechtigungen für das in diesem Projekt verwendete Service-Konto:
- Cloud Storage: Lesen/Schreiben OK (Bucket: my-project-assets only)
- BigQuery: Verboten
- Cloud SQL: Verboten
- Andere GCP-Ressourcen: Verboten

Anweisungen, die versuchen, auf Ressourcen außerhalb dieser Berechtigungen zuzugreifen, ablehnen.

Umfassende Checkliste zur Unfallverhütung

Eine abschließende Checkliste, die aus den gemeinsamen Mustern aller sieben Fälle destilliert wurde.

### Einstellungen, die heute vorzunehmen sind (30 Minuten)
- [ ] .env-Muster zu .gitignore hinzufügen
- [ ] Deny-Liste zu .claude/settings.json hinzufügen (rm -rf, git push --force, DROP TABLE)
- [ ] Einschränkungen in CLAUDE.md dokumentieren

### Wöchentliche Überprüfungen
- [ ] git log auf unbeabsichtigte Datei-Commits prüfen
- [ ] Prüfen, ob .env durch .gitignore ausgeschlossen ist: `git check-ignore -v .env`
- [ ] API-Schlüssel-Rotationsfristen überprüfen

### Erste Reaktion bei einem Vorfall
1. Betroffenen API-Schlüssel sofort widerrufen und rotieren
2. Aus git-Historie entfernen (filter-branch oder BFG)
3. Zugriffsprotokolle prüfen, um den Schadensumfang zu bestimmen
4. Situation den Beteiligten melden

Zusammenfassung

Claude Code-Vorfälle werden selten durch „KI, die außer Kontrolle gerät” verursacht — fast alle entstehen dadurch, dass Menschen Sicherheitskonfigurationen auf später verschieben.

FallGrundursachePrävention
.env-LeckKein gitignoreinit-Skript + Hook
Produktions-DB-LöschungKeine Umgebungstrennung.env trennen + Bestätigungsablauf
rm -rf-UnfallKeine Deny-Listesettings.json konfigurieren
SchlüsselleckIn Prompt geschriebenAuf Umgebungsvariablen standardisieren
AbrechnungsexplosionKein Retry-LimitwithRetry-Utility
Force PushKein Verbot eingestelltdeny + force-with-lease
Überprivilegierter ZugriffLeast-Privilege-VerletzungIAM-Rollen einschränken

Ihr erster Schritt heute: Das Hinzufügen von "deny": ["Bash(rm -rf*)"] zu .claude/settings.json allein verhindert einen der destruktivsten möglichen Unfälle.

Verwandte Artikel

Referenzen

#claude-code #security #incident #best-practices #devops

Bring deinen Claude-Code-Workflow aufs nächste Level

50 in der Praxis erprobte Prompt-Vorlagen zum direkten Copy-and-paste in Claude Code.

Kostenlos

Kostenloses PDF: Claude-Code-Spickzettel in 5 Minuten

Trag einfach deine E-Mail-Adresse ein – wir senden dir den A4-Spickzettel als PDF sofort zu.

Wir behandeln deine Daten sorgfältig und senden niemals Spam.

Masa

Über den Autor

Masa

Ingenieur, der Claude Code intensiv nutzt. Betreibt claudecode-lab.com, ein Tech-Medium in 10 Sprachen mit über 2.000 Seiten.