Sicherheitsdenken beim Vibe Coding mit Cursor

Vibe Coding mit Cursor ist schnell und macht Spaß: Sie beschreiben, was Sie wollen, der Agent schreibt den Code, und Sie halten den Fluss aufrecht, ohne bei jeder Zeile anzuhalten und sie zu lesen. Das Problem ist, dass ein Modell, das darauf trainiert ist, Prompts zu erfüllen, bereitwillig Code produziert, der funktioniert und sauber aussieht, aber dennoch SQL-Injection, fest codierte API-Schlüssel oder eine Autorisierungsprüfung enthält, die dem Client vertraut. Dieser Leitfaden gibt Ihnen eine wiederholbare Sicherheitsdenkweise für den Vibe-Coding-Workflow in Cursor – vom Erkennen der Fehler, die KI-generierter Code am häufigsten produziert, über das Prüfen von Ausgaben, das Durchsetzen von Rules und den Umgang mit Secrets bis hin zu einer Checkliste vor der Bereitstellung, bevor irgendetwas ausgeliefert wird.
Was ist Vibe Coding und warum Sicherheit zählt
Vibe Coding bedeutet, dass Sie mit Absicht steuern und das Modell die Implementierungsdetails ausfüllen lassen. Sie bitten um „einen Login-Endpunkt mit einem Sitzungs-Cookie“, und Cursor schreibt die Route, den Datenbankaufruf und die Middleware. Die Ausgabe ist meist funktionsfähig, aber funktionsfähig ist nicht dasselbe wie sicher. Das Modell optimiert darauf, dass der Code läuft, nicht auf die Verteidigung gegen einen Angreifer, der denselben Code liest.
Sicherheit zählt in diesem Workflow mehr, nicht weniger, aus drei Gründen:
- Sie prüfen weniger. Wenn das Modell dutzende Zeilen pro Prompt schreibt, wird der menschliche Prüfschritt zum Engpass und wird übersprungen.
- Das Modell hat aus unsicherem Code gelernt. Öffentliche Repositories enthalten reichlich string-konkateniertes SQL und clientseitige Token-Speicherung, sodass diese Muster in Vervollständigungen auftauchen.
- Der Wirkungsradius ist groß. Der Agent-Modus kann viele Dateien gleichzeitig berühren, sodass ein einzelnes unsicheres Muster über die Codebasis dupliziert wird.
Sicherheit ist keine separate Phase, die Sie am Ende anflanschen. Es ist eine konstante Gewohnheit, die Sie darin einbauen, wie Sie mit Cursor prompten, prüfen und deployen.
Cursors Agent kann Code schreiben, der kompiliert, Ihre Tests besteht und dennoch eine Schwachstelle ausliefert. Das Modell kann Ihr Risiko nicht absegnen. Behandeln Sie jeden generierten Block als Entwurf eines schnellen, aber sorglosen Junior-Entwicklers.
Die häufigsten Schwachstellen in KI-generiertem Code
Dies sind die Fehlermuster, die in KI-generiertem Code immer wieder auftauchen. Lernen Sie, die Form jedes einzelnen zu erkennen, damit Sie es beim Überfliegen entdecken, statt jede Zeile zu lesen.
| Schwachstelle | Wie sie in generiertem Code aussieht | Warum das Modell sie erzeugt |
|---|---|---|
| SQL-Injection | Abfrage mit String-Konkatenation statt Parametern gebaut | Konkatenation ist einfacher zu schreiben und liest sich „natürlich“ |
| XSS | Unsanitisierte Benutzereingabe mit innerHTML oder dangerouslySetInnerHTML gerendert | HTML direkt zu rendern ist der kürzeste Weg zum visuellen Ergebnis |
| Fest codierte Secrets / API-Schlüssel | const API_KEY = "sk-live-..." in der Quelldatei belassen | Ein Literalwert macht das Snippet eigenständig und lauffähig |
| Fehlerhafte Auth / Sitzungsbehandlung | Tokens in localStorage gespeichert, kein Ablauf, keine serverseitige Validierung | Client-Speicherung ist im Browser am einfachsten zu demonstrieren |
| Permissives CORS | Access-Control-Allow-Origin: * mit Credentials | Der Wildcard ist die Ein-Zeilen-Lösung, die den unmittelbaren Fehler stoppt |
| Unsichere Deserialisierung | pickle.loads, eval oder blindes Vertrauen in JSON-zu-Objekt | Alles zu deserialisieren ist bequem und setzt vertrauenswürdige Eingabe voraus |
| Offengelegte Admin-Endpunkte | Routen wie /admin/users ohne Rollenprüfung | Das Modell implementiert das Feature, vergisst aber die Absicherung |
| Fehlende Eingabevalidierung | Body direkt in der Logik verwendet, ohne Schema-Prüfung | Validierung wirkt redundant, wenn der Happy Path schon funktioniert |
SQL-Injection in JavaScript
Hier das klassische Paar: eine Abfrage, die Benutzereingabe konkateniert, und dieselbe Abfrage mit parametrisierten Platzhaltern geschrieben.
// VULNERABLE: user input is concatenated straight into the query
app.get('/user', async (req, res) => {
const sql = "SELECT * FROM users WHERE email = '" + req.query.email + "'";
const rows = await db.query(sql);
res.json(rows);
});
// FIXED: parameterized query, input never becomes part of the SQL string
app.get('/user', async (req, res) => {
const sql = 'SELECT * FROM users WHERE email = $1';
const rows = await db.query(sql, [req.query.email]);
res.json(rows);
});
Autorisierungsprüfungen in Python
Die verwundbare Version vertraut einem vom Client gesendeten Flag. Die korrigierte Version prüft die authentifizierte Sitzung auf dem Server.
# VULNERABLE: the client tells the server whether it is an admin
@app.get("/admin/users")
def admin_users(request):
if request.args.get("is_admin") == "true":
return db.list_users()
return {"error": "forbidden"}
# FIXED: the server decides based on the authenticated session
@app.get("/admin/users")
def admin_users(request):
user = request.session.get("user")
if not user or user["role"] != "admin":
abort(403)
return db.list_users()
Token-Speicherung: localStorage vs. httpOnly-Cookie
Ein Sitzungstoken in localStorage zu speichern macht es für jedes Skript auf der Seite lesbar, was einen einzelnen XSS-Bug in eine vollständige Kontoübernahme verwandelt.
// VULNERABLE: token readable by any script, no protection against XSS
localStorage.setItem('token', response.token);
// FIXED: server sets an httpOnly cookie that scripts cannot read
res.cookie('session', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 1000 * 60 * 60,
});
KI-generierten Code mit Cursor prüfen
Prüfen bedeutet nicht, jede Zeile zu lesen. Es bedeutet, eine zweite Runde des Modells zu bitten, das anzugreifen, was die erste Runde geschrieben hat. Cursor ist gut darin, wenn Sie ihm die richtige Persona und ein klares Ziel geben.
Ein Prompt für die Sicherheitsprüfungs-Persona
Verwenden Sie diesen Prompt im Agent-Modus erneut, nachdem der Agent ein Feature fertiggestellt hat. Er zwingt das Modell, vom Erbauer zum Angreifer zu wechseln.
You are a security reviewer auditing code a previous agent just wrote.
For every file changed in this feature:
1. List each place that handles user input, authentication, or secrets.
2. For each, describe the concrete attack an unauthenticated user could run.
3. Show the vulnerable line and the fixed line side by side.
4. Rate severity as low, medium, high, or critical with one sentence of reasoning.
Do not rewrite the whole feature. Report findings only.
Um Threat Modeling im Agent-Modus bitten
Über das Prüfen fertigen Codes hinaus können Sie Cursor bitten, ein Threat Model zu erstellen, bevor es schreibt. Dies bringt Designfehler zutage, die eine Prüfung auf Zeilenebene übersieht.
Before implementing this billing endpoint, do a short threat model:
- Who can call this route and with what privileges?
- What happens if the amount, currency, or user ID is tampered with?
- Which trust boundary does this input cross?
Then implement the endpoint with those mitigations in place.
Mit .cursor/rules Prüfungen erzwingen
Sie können die Prüfung selbst als Rule kodieren, sodass der Agent sie bei jeder Änderung automatisch ausführt. Rules in .cursor/rules/ werden in den Kontext geladen, was bedeutet, dass die Sicherheits-Checkliste immer präsent ist, wenn der Agent arbeitet.
Machen Sie „eine Sicherheitsprüfung ausführen“ zu einem Reflex nach jeder Agent-Sitzung, die Auth, Eingabebehandlung oder Secrets berührt. Es kostet einen Prompt und fängt die Fehlermuster aus der obigen Tabelle ab, bevor sie einen Commit erreichen.
Sicherheit mit Cursor Rules durchsetzen
Eine Projekt-Rule-Datei hält Ihre Sicherheitserwartungen vor dem Modell, ohne sich zu wiederholen. Erstellen Sie .cursor/rules/security.mdc und committen Sie sie, damit jeder Mitarbeiter dieselben Leitplanken erhält.
---
description: Security requirements for all code in this project
globs: ["**/*.ts", "**/*.tsx", "**/*.py", "**/*.js"]
alwaysApply: true
---
# Security requirements
- Never write SQL by concatenating user input. Use parameterized queries or an ORM.
- Never render user input with innerHTML or dangerouslySetInnerHTML. Escape it or use a safe renderer.
- Never hardcode secrets, API keys, or tokens. Read them from environment variables.
- Never grant administrative access based on a value supplied by the client.
- Always validate request bodies against a schema before use.
- Always set httpOnly, secure, and sameSite on session cookies.
- Never use eval, exec, or pickle.loads on untrusted input.
- When adding a new endpoint, state explicitly which role can call it.
- When touching auth or input handling, add a security-review note in the response.
Mit alwaysApply: true wird die Rule in jede Anfrage injiziert, sodass der Agent sich selbst korrigiert, während er schreibt, statt dass Sie Probleme später abfangen. Sie können Rules auch mit globs auf bestimmte Ordner begrenzen, damit Backend-Sicherheitsregeln die Frontend-Arbeit nicht überladen.
Secrets und Umgebungsvariablen verwalten
Fest codierte Secrets sind die mit Abstand am einfachsten versehentlich ausgelieferte Schwachstelle, weil ein Literalwert Code lauffähig macht und das Modell lauffähigen Code mag. Halten Sie Secrets vollständig aus dem Quellcode heraus.
Muster für Umgebungsvariablen
# .env.example -- commit this, it documents the shape without real values
DATABASE_URL=postgres://user:password@localhost:5432/app
SESSION_SECRET=replace-me
STRIPE_SECRET_KEY=replace-me
# Load locally, never commit the real .env
cp .env.example .env
# .env -- gitignored, contains the real values and stays on your machine
DATABASE_URL=postgres://realuser:realpass@prod-host:5432/app
SESSION_SECRET=a-long-random-value-from-a-secret-manager
STRIPE_SECRET_KEY=sk_live_the_actual_key
Fügen Sie .env und verwandte Dateien immer vor Ihrem ersten Commit zu .gitignore hinzu, nicht danach.
# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key
Secret-Scanning vor dem Commit mit gitleaks
Ein Pre-Commit-Hook stoppt ein Secret, bevor es überhaupt die Repository-Historie erreicht, was weit günstiger ist als das Rotieren eines geleakten Schlüssels.
# Install gitleaks (Homebrew example)
brew install gitleaks
# Scan the whole repo once
gitleaks detect --source . --verbose
# Or install it as a pre-commit hook
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.18.0
hooks:
- id: gitleaks
pre-commit install
pre-commit run --all-files
Wenn ein echtes Secret jemals in einem Commit landet, rotieren Sie die Credentials sofort. Die Zeile zu löschen oder die Historie neu zu schreiben entleakt keinen Schlüssel, der bereits auf ein Remote gepusht wurde.
Authentifizierung und Autorisierung manuell validieren
Automatisierte Scans verpassen Logikfehler, daher müssen Sie den Ablauf weiterhin von Hand verifizieren. Gehen Sie diese Prüfungen gegen eine laufende Instanz durch.
Sitzungs- und Auth-Prüfungen
- Melden Sie sich ab und fordern Sie dann eine geschützte Route mit dem alten Token an. Sie muss mit 401 fehlschlagen, nicht Daten zurückgeben.
- Ändern Sie Ihre eigene Benutzer-ID in einer URL oder einem Body auf die ID eines anderen Nutzers. Der Server muss sie ablehnen, nicht dem Parameter vertrauen.
- Rufen Sie eine Admin-Route als Nicht-Admin auf. Sie muss 403 zurückgeben, unabhängig von einem clientseitig gelieferten Flag.
- Bestätigen Sie, dass Cookies mit
httpOnly,secureund einem Ablauf gesetzt werden. Öffnen Sie die Browser-Konsole und versuchen Sie, das Cookie mitdocument.cookiezu lesen; es sollte nicht erscheinen. - Setzen Sie ein Passwort zurück und verifizieren Sie, dass die alte Sitzung ungültig gemacht wird.
Token- und Header-Prüfungen
# A protected route without credentials must not return data
curl -i https://api.example.com/me
# A protected route with a forged or expired token must fail
curl -i https://api.example.com/me -H "Authorization: Bearer forged.token.value"
# An admin route as a normal user must return 403
curl -i https://api.example.com/admin/users -H "Authorization: Bearer $USER_TOKEN"
Führen Sie diese gegen eine Staging-Umgebung aus, die die Produktion spiegelt, nicht nur gegen localhost, wo die Daten Wegwerf sind.
Abhängigkeiten und Lieferkette scannen
Ihr eigener Code ist nur ein Teil der Angriffsfläche. Generierter Code zieht Abhängigkeiten herein, und ein verwundbares Paket ist eine Schwachstelle, die Sie ausgeliefert haben. Scannen Sie den Baum bei jeder Änderung.
# Node / npm -- built in, no extra install
npm audit
npm audit --production
# Python
pip-audit
pip-audit -r requirements.txt
# Snyk for a cross-language view
npm install -g snyk
snyk test
snyk monitor
# Or run the Snyk CLI against a Python project
pip install snyk
snyk test --file=requirements.txt
Verdrahten Sie einen Scanner in CI, damit er bei jedem Pull Request läuft, statt sich auf das Gedächtnis zu verlassen.
# .github/workflows/security.yml (excerpt)
- name: Scan dependencies
run: npm audit --production --audit-level=high
Bevor Sie ein von einem Modell vorgeschlagenes Paket akzeptieren, prüfen Sie seine Download-Zahl, das letzte Veröffentlichungsdatum und den Maintainer. Typosquatting-Pakete sind ein realer Angriffsvektor, und ein Modell kann einen Namen vorschlagen, der nah an der echten Bibliothek liegt, es aber nicht ist.
Eine Sicherheits-Checkliste vor der Bereitstellung
Arbeiten Sie diese Liste vor jedem Deploy durch. Sie ist bewusst kurz, damit Sie sie tatsächlich verwenden.
- Keine fest codierten Secrets, API-Schlüssel oder Tokens in irgendeiner committeten Datei
-
.envund Schlüsseldateien stehen in.gitignoreund wurden nie committet - Alles SQL verwendet parametrisierte Abfragen oder ein ORM, ohne String-Konkatenation
- Alle Benutzereingaben werden vor der Verwendung gegen ein Schema validiert
- Sitzungstokens werden in httpOnly-Cookies gespeichert, nicht in
localStorage - Jede geschützte Route gibt 401/403 zurück, wenn nicht authentifiziert oder autorisiert
- Admin-Routen verifizieren die Rolle serverseitig, nie aus einem clientseitig gelieferten Wert
- CORS erlaubt nur Ihre bekannten Origins, nicht
*mit Credentials -
npm audit/pip-auditmelden keine hohen oder kritischen Befunde - Fehlerantworten leaken keine Stack Traces, kein SQL und keine internen Pfade
- Logging erfasst keine Tokens, Passwörter oder vollständigen Request-Bodys
- Eine Sicherheitsprüfungs-Runde lief für jede Datei, die der Agent in diesem Sprint berührt hat
Befunde melden und beheben
Befunde sind nur nützlich, wenn sie behoben und nachverfolgt werden. Geben Sie jedem gerade genug Struktur, um darauf zu handeln.
- Den Befund festhalten. Eine Zeile mit der Datei, der Zeile, dem Schwachstellentyp und dem Schweregrad.
- Die Behebung als Diff schreiben. Lassen Sie Cursor die Änderung von der verwundbaren zur korrigierten Zeile erzeugen und prüfen Sie sie dann.
- Einen Test hinzufügen, der die Behebung beweist. Ein Regressionstest für die Injection oder die Auth-Umgehung hält sie davon ab, zurückzukehren.
- Die Rule aktualisieren. Wenn der Agent den Bug produziert hat, fügen Sie eine Zeile zu
.cursor/rules/security.mdchinzu, damit er ihn nicht erneut produziert. - Erneut testen und schließen. Führen Sie die manuellen Prüfungen und den Scanner gegen den korrigierten Build erneut aus.
Finding: CWE-89 SQL injection, src/api/users.ts:42, severity high
Vulnerable: db.query("SELECT * FROM users WHERE id = " + id)
Fixed: db.query("SELECT * FROM users WHERE id = $1", [id])
Regression test: request /user?id=1' OR '1'='1 returns no rows
Rule update: added "never concatenate user input into SQL" to security.mdc
Status: fixed, verified in staging
Behandeln Sie die Rule-Datei als wachsendes Protokoll jedes Fehlers, den der Agent gemacht hat. Mit der Zeit wird sie zum wertvollsten Sicherheits-Asset im Repository, weil sie genau festhält, wie Ihre Codebasis zuvor falsch lag.