// docs / security guides / hardening
So sicherst du eine App, die mit KI-Coding-Tools gebaut wurde
Eine Schritt-für-Schritt-Anleitung zum Härten von Apps, die Sie mit Cursor, Claude Code, Lovable, Bolt, v0, Replit oder Windsurf erstellt haben. Vier Phasen: Verstehen Sie, warum AI-generierte Apps unterschiedlich ausfallen, führen Sie ein sofortiges Codebasis-Audit durch, härten Sie es zum Zeitpunkt der Bereitstellung aus und führen Sie dann die Überwachung fort. Meinungsstark, erzählerisch, mit echten Ausschnitten zum Nachmachen.
Warum AI-generierte Apps unterschiedlich scheitern
Vibe-codierte Apps können sicher sein. Sie benötigen einen zusätzlichen Prüfdurchgang, da die Fehlerarten struktureller und nicht unachtsamer Natur sind:
- Cursor inlines hardcoded keys. Sie bitten Cursor, „den Authentifizierungsfehler zu beheben“, und es wird ein Supabase-Beispiel eingefügt, das einen Client mit Dienstrolle annimmt. Der Schlüssel landet oben in einer Seitenkomponente. Sowohl der anonyme Client als auch der Service-Client existieren nebeneinander. beide Schiffe.
- Claude Code defaults to permissive CORS. Generierte Express-/Fastify-Handler werden mit
cors({ origin: '*' })ausgeliefert, da dies der schnellste Weg ist, eine funktionierende Vorschau zu erhalten. Die Middleware erhält nie einen zweiten Durchgang. - Lovable and v0 skip the rules file. Von Firestore unterstützte Projekte generieren das Datenmodell, berühren aber selten
firestore.rules. Testmodusregeln laufen stillschweigend ab und sperren die Datenbank, ohne dass der Benutzer eine Warnung erhält. - Bolt skips RLS migrations. Bolt generiert ein Supabase-Schema und eine CRUD-Oberfläche, die den anonymen Schlüssel verwendet.
ENABLE ROW LEVEL SECURITYnimmt nie an der Migration teil. Anonyme Benutzer können jede Zeile lesen oder schreiben. - Windsurf trusts unsigned IDs. Das generierte
GET /api/items/[id]liest den Parameter und fragt Postgres ab, ohne den Besitz zu überprüfen. Das Muster ist so häufig, dass die aktive active.idor-walking Sonde es innerhalb eines einzigen Scans entdeckt.
Die unmittelbare Prüfung: Durchsuchen Sie Ihre Codebasis nach Risikomustern
Bevor Sie etwas aushärten, finden Sie heraus, was bereits kaputt ist. Diese Greps dauern jeweils weniger als eine Minute:
Geheimnisse und Anbieterschlüssel
grep -RIn 'NEXT_PUBLIC_SUPABASE_SERVICE' src/
grep -RIn 'sk_live_\|pk_live_\|STRIPE_SECRET' src/
grep -RIn 'sk-ant-\|^sk-' src/ # Anthropic / OpenAI
grep -RIn 'AIza\|AKIA' src/ # Google / AWS
grep -RIn 'eyJh[A-Za-z0-9_-]\{20,\}' src/ # JWT-shaped stringsJeder Treffer erfordert eine Löschung plus Schlüsselrotation. Provider-Dashboards: Supabase → Einstellungen → API, Stripe → Entwickler → API Schlüssel, Anthropic / OpenAI-Konsole.
Datenbankzugriffskontrollen
# Supabase migrations
grep -RIn 'CREATE TABLE public\.' supabase/migrations/
grep -RIn 'ENABLE ROW LEVEL SECURITY\|FORCE ROW LEVEL SECURITY' supabase/migrations/
# Firebase / Firestore
cat firestore.rules # confirm no `if true;` matchesJedes CREATE TABLE public.* benötigt ein passendes ENABLE ROW LEVEL SECURITY und mindestens eine Richtlinie. Firestore-Regeln müssen den Lesebereich auf request.auth.uid beschränken.
Authentifizierungs- und Sitzungsverwaltung
grep -RIn 'getSession()' src/ # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/Vom Server gerenderte Routen müssen supabase.auth.getUser() verwenden – die Überprüfung erfolgt mit dem Backend. getSession() liest ein nicht verifiziertes Cookie. Auf Token in localStorage kann jedes Skript zugreifen, das auf der Seite ausgeführt wird.
Header und Middleware
# Confirm middleware location for src/ layouts
ls src/middleware.ts middleware.ts 2>&1
# Look for CSP and security headers
grep -RIn 'Content-Security-Policy\|Strict-Transport-Security' src/Beim src/-Layout wird nur src/middleware.ts aufgenommen. Wenn sich Ihre Middleware-Datei im Projektstammverzeichnis befindet, wird sie von Next.js stillschweigend ignoriert und Ihre CSP / auth-refresh-Logik wird nie ausgeführt.
Verhärtung zum Zeitpunkt der Bereitstellung
Sobald die Quelle bereinigt ist, sperren Sie, wie die App in die Produktion gelangt.
Schritt 1: Separate Umgebungen
Vercel: drei Umgebungen – Production (Ihre Produktionsdomäne), Vorschau (PR / Staging-Bereitstellungen), Entwicklung (lokal). Jeder erhält seinen eigenen Env-Var-Satz. Live Stripe / Anthropic / Supabase Keys erreichen nie die Vorschau; Vorschauschlüssel erreichen nie Production. Zweige werden automatisch in die Vorschau verschoben; Zusammenführen zu main wird zu Production bereitgestellt.
Schritt 2: Striktes CSP über Middleware
Generieren Sie eine Nonce pro Anfrage und fügen Sie sie dann in Content-Security-Policy ein. Next.js wendet die Nonce automatisch auf seine eigenen Skript-Tags an, wenn Sie den x-nonce-Anforderungsheader festlegen.
// src/middleware.ts
import { NextResponse, type NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const nonce = crypto.randomUUID().replace(/-/g, '');
const csp = [
`script-src 'nonce-${nonce}' 'strict-dynamic'`,
`style-src 'self' 'unsafe-inline'`,
`img-src 'self' data: https:`,
`connect-src 'self' https://*.supabase.co`,
`object-src 'none'`,
`base-uri 'self'`,
`frame-ancestors 'none'`,
].join('; ');
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-nonce', nonce);
const response = NextResponse.next({ request: { headers: requestHeaders } });
response.headers.set('Content-Security-Policy', csp);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return response;
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
};Schritt 3: Erzwingen Sie RLS für jede öffentliche Tabelle
RLS ist standardmäßig nicht aktiviert und wird für Tabellenbesitzer nicht erzwungen, es sei denn, Sie FORCE es. Koppeln Sie jede Tabelle mit expliziten Richtlinien pro Rolle.
-- supabase/migrations/XXXX_rls.sql
alter table public.profiles enable row level security;
alter table public.profiles force row level security;
create policy "profiles: read own"
on public.profiles for select
using (auth.uid() = id);
create policy "profiles: update own"
on public.profiles for update
using (auth.uid() = id)
with check (auth.uid() = id);Schritt 4: Nur-Server-Authentifizierungsüberprüfung auf jeder API-Route
Jede zustandsverändernde API-Route verifiziert den Aufrufer serverseitig mit supabase.auth.getUser(). Das Benutzerobjekt wird zur Quelle der Wahrheit für user_id – vertrauen Sie niemals einem Anforderungstext, der es festlegt.
// src/app/api/items/route.ts
import { NextResponse, type NextRequest } from 'next/server';
import { createClient } from '@/lib/supabase/server';
export async function POST(request: NextRequest) {
const supabase = await createClient();
const { data: { user } } = await supabase.auth.getUser();
if (!user) return NextResponse.json({ error: 'unauthorized' }, { status: 401 });
const body = await request.json();
const { data, error } = await supabase
.from('items')
.insert({ ...body, user_id: user.id }) // server-supplied, not from body
.select()
.single();
if (error) return NextResponse.json({ error: error.message }, { status: 400 });
return NextResponse.json(data);
}Schritt 5: Reverse-Proxy Ihrer Analysen
Proxying-Analysen über Ihre eigene Domain vermeiden Werbeblocker und sorgen dafür, dass Ihr CSP connect-src 'self' eng bleibt. Das gleiche Muster funktioniert für PostHog, Plausible, Umami und benutzerdefinierte Ereignissenken.
// src/app/api/posthog/[...path]/route.ts
import { type NextRequest } from 'next/server';
const UPSTREAM = 'https://us.i.posthog.com';
export async function POST(req: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
const { path } = await params;
const url = `${UPSTREAM}/${path.join('/')}`;
return fetch(url, {
method: 'POST',
headers: { 'content-type': req.headers.get('content-type') ?? 'application/json' },
body: await req.text(),
});
}Schritt 6: Öffnen Sie den Redirect-Schutz für den Post-Authentifizierungs-Bounce
Anmelde-/Registrierungsabläufe akzeptieren üblicherweise einen next-Abfrageparameter. Lehnen Sie alles ab, was kein Pfad zur gleichen Site ist – beginnen Sie mit / und niemals mit // (protokollbezogen, leitet Benutzer von der Site ab).
function safeNext(raw: string | null): string {
if (!raw) return '/dashboard';
if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
return raw;
}Laufend: Überwachung und erneutes Scannen
Bei jedem Einsatz kommt es zu Drift. Behandeln Sie Sicherheit als eine Schleife und nicht als eine Checkliste, die Sie durcharbeiten.
Überprüfen Sie Ihre Produktionsdomäne
Dashboard → Domains → Fügen Sie Ihre Produktdomäne hinzu → DNS TXT oder HTTP-Dateiüberprüfung (Einzelschritt). Nach der Überprüfung stehen aktive Scans zur Verfügung und geplante erneute Scans können aktiviert werden.
Planen Sie passive erneute Scans
Täglich um Hobby, 3-stündlich um Pro, stündlich um Unlimited. Bei jedem Durchlauf erhalten Sie eine E-Mail, wenn ein neuer Befund erscheint, und löst einen scan.completed-Webhook aus, wenn Sie sich angemeldet haben.
# Or from CI, via the REST API:
curl -X POST https://fixvibe.app/api/v1/scans \
-H "authorization: Bearer $FIXVIBE_TOKEN" \
-H "content-type: application/json" \
-d '{"target":"https://your-app.com"}'API-aktive Scans aktivieren (optional)
Wenn Sie eine automatisierte aktive Prüfung (SQLi / XSS / IDOR Gehen / usw.) wünschen, schalten Sie sie pro Domäne unter Dashboard → Domänen → API aktiv ein. Die Autorisierung ist dauerhaft, läuft 90 Tage ab und kann sofort widerrufen werden. Koppeln Sie es mit dem scan.active_api.first_used-Webhook, damit der erste automatisierte aktive Scan nach der Aktivierung Ihre Benachrichtigung erreicht.
Integrieren Sie die Ergebnisse in Ihren AI Workflow
Mint an API token at Account → API tokens, then configure the MCP server (/docs/mcp) in Claude Desktop / Cursor / Continue. Ask your agent: "Run a scan on staging and show me the highest-severity findings." The agent calls FixVibe, fetches the report, and renders categorized remediation guidance so code/config fixes become prompts and DNS/provider/manual fixes become operator steps.
Live-Bedrohungserkennung (Unlimited)
Zertifikatstransparenz-Protokollunterschiede zeigen neue TLS Zertifikate an, die für Ihre Domain ausgestellt wurden. DNS-Datensatzunterschiede fangen nicht autorisierte Änderungen ab. Die Überwachung des JS-Bundle-Geheimnisses wird ausgelöst, sobald ein neuer Schlüssel ein versendetes Bundle erreicht. Threat-Intel-Feeds (Spamhaus, URLhaus) melden Ihre Domain, wenn sie aufgeführt ist.
Echte Fehlermuster und deren Behebung
Fünf Muster aus Produktionsscans in Tausenden von AI-generierten Apps, jeweils mit der tatsächlichen Fehlerbehebung:
- Dienstrollenschlüssel in einer Clientkomponente
Symptom:
baas.supabase-service-keyFeststellung zur Produktion URL. Cause: eine Cursor automatische Vervollständigung, diecreateClient(URL, SERVICE_ROLE_KEY)in eine React-Komponente eingefügt hat. Fix: verschiebt den Service-Client nachsrc/lib/supabase/service.tsmitimport 'server-only'oben; Erstellen Sie ein parallelessrc/lib/supabase/client.tsmit dem anonymen Schlüssel für die clientseitige Verwendung. Drehen Sie den Dienstrollenschlüssel über Supabase Studio. - Firestore-Regeln bleiben im Testmodus
Symptom:
baas.firebase-rulesBefund mit hohem Schweregrad. Cause: generierte Regeln lautenallow read, write: if request.time < timestamp.date(2026, 6, 1);– ein zeitbegrenztes „Alles zulassen“. Fix: beschränkt jede Regel auf den authentifizierten Benutzer –match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; }– und stelltfirebase deploy --only firestore:ruleserneut bereit. - Freizügiges CORS Überleben in der Produktion
Symptom:
active.corshoher Schweregrad. Cause: generierte Express-Middleware:app.use(cors({ origin: '*' })). Fix: Setzen Sie Ihren Frontend-Ursprung auf die Zulassungsliste:app.use(cors({ origin: ['https://your-app.com'], credentials: true })). Für Next.js API Routen legen SieAccess-Control-Allow-Originexplizit in der Antwort fest. - RLS aktiviert, aber nicht erzwungen
Symptom: aktiv
baas.supabase-rlsmeldet, dass die anonyme Rolle in eine öffentliche Tabelle schreiben kann, obwohl RLS im Dashboard aktiviert ist. Cause:ENABLEohneFORCElässt den Tabelleneigentümer ausgenommen – und Migrationen werden als Eigentümer ausgeführt. Fix:alter table public.items force row level security;an die Migration anhängen. Erneut bereitstellen. - Nicht signierte IDOR-begehbare IDs
Symptom:
active.idor-walkingmeldet, dass der anonyme Benutzer/api/items/1,/api/items/2, ... mandantenübergreifend lesen kann. Cause: Der API Handler vertraut dem Pfadparameter und fragt Postgres ohne Besitzprädikat ab. Fix: Fügen Sie.eq('user_id', user.id)bei jeder Leseabfrage hinzu oder wechseln Sie zu signierten URLs/UUIDs mit dem Gültigkeitsbereich/api/users/[uid]/items/[id].
Die Vibe-Code-Sicherheitsschleife
Das Ziel ist nicht perfekte Sicherheit; Es eliminiert die niedrig hängenden Früchte, die AI Tools ständig übersehen, sodass Sie weiterhin schnell versenden können.
- Generate fast – verwenden Sie Cursor, Claude Code, Lovable, Bolt. Das ist der Punkt.
- Audit immediately – Führen Sie den oben angegebenen grep-Satz aus, überprüfen Sie RLS, überprüfen Sie CSP und überprüfen Sie die Authentifizierungsgrenze.
- Harden at deploy – Middleware, Umgebungstrennung, CSP Nonce, HSTS, reine Server-Authentifizierungsüberprüfung.
- Monitor – FixVibe täglich passiv, wöchentlich aktiv auf einer verifizierten Domain, Webhooks zu Slack, Bedrohungserkennung auf Unlimited.
- Fix fast — use FixVibe coding-agent prompts for code/config findings and operator steps for DNS, provider, secret-rotation, or manual-review findings. Re-deploy, re-scan, close the loop.
Nächste Schritte
Für den konzeptionellen Hintergrund zu DAST vs. SAST und warum AI-generierte Apps einen eigenen Scan benötigen, lesen Sie AI-generated code security scanning. Eine Kurzreferenz vor dem Versand finden Sie im vibe coding security checklist.
