FixVibe

// docs / security guides / hardening

Comment sécuriser une app construite avec des outils de codage IA

Un guide de renforcement étape par étape pour les applications que vous avez créées avec Cursor, Claude Code, Lovable, Bolt, v0, Replit ou Windsurf. Quatre phases : comprendre pourquoi AI-les applications générées échouent différemment, exécuter un audit immédiat de la base de code, renforcer au moment du déploiement, puis continuer la surveillance. Opinion, narrative, avec de vrais extraits que vous pouvez copier.

Pourquoi AI-les applications générées échouent différemment

Les applications codées par Vibe peuvent être sécurisées. Ils ont besoin d’un audit supplémentaire car les modes de défaillance sont structurels et non imprudents :

  • Assistants inline hardcoded keys. You ask for a fix to an auth error and get a pasted Supabase example that assumes a service-role client. The key ends up at the top of a page component. Both the anon client and the service client coexist; both ship.
  • Generated servers default to permissive CORS. Generated Express / Fastify handlers often ship with cors({ origin: '*' }) because that's the fastest way to get a working preview. The middleware never gets a second pass.
  • Rules files get skipped. Firestore-backed projects generate the data model but rarely touch firestore.rules. Test-mode rules let anyone read and overwrite data until someone replaces them.
  • RLS never enters the migration. A generated Supabase schema and CRUD surface use the anon key, but ENABLE ROW LEVEL SECURITY never enters the migration. Anonymous users can read or write any row.
  • Handlers trust IDs. A generated GET /api/items/[id] reads the param and queries Postgres without verifying ownership. Active scans on a verified domain test for this (IDOR / BOLA).

L'audit immédiat : recherchez votre base de code pour les modèles de risque

Avant de durcir quoi que ce soit, trouvez ce qui est déjà cassé. Ces greps prennent chacun moins d'une minute :

Secrets et clés du fournisseur

bash
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 strings

Tout accès nécessite une suppression et une rotation des clés. ProTableaux de bord Vider : Supabase → Paramètres → API, Stripe → Développeurs → API touches, console Anthropic / OpenAI.

Contrôles d'accès à la base de données

bash
# 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;` matches

Chaque CREATE TABLE public.* nécessite un ENABLE ROW LEVEL SECURITY correspondant et au moins une stratégie. Les règles Firestore doivent étendre les lectures à request.auth.uid.

Gestion de l'authentification et de la session

bash
grep -RIn 'getSession()' src/   # should be getUser() server-side
grep -RIn 'localStorage\.\(set\|get\)Item.*token' src/
grep -RIn 'jwt.verify.*\(noVerify\|skipVerify\)' src/

Les routes rendues par le serveur doivent utiliser supabase.auth.getUser() — elles sont vérifiées auprès du backend. getSession() lit un cookie non vérifié. Les jetons dans localStorage sont accessibles à tout script exécuté sur la page.

En-têtes et middleware

bash
# 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/

Avec la disposition src/, seul src/middleware.ts est récupéré. Si votre fichier middleware se trouve à la racine du projet, Next.js l'ignore silencieusement et votre logique CSP / auth-refresh ne s'exécute jamais.

Durcissement au moment du déploiement

Une fois la source propre, verrouillez la manière dont l’application atteint la production.

Étape 1 : Séparer les environnements

Vercel : trois environnements — Production (votre domaine de production), Aperçu (PR / déploiements intermédiaires), Développement (local). Chacun obtient son propre ensemble de variables d'environnement. Les touches Live Stripe / Anthropic / Supabase n'atteignent jamais l'aperçu ; Les clés d'aperçu n'atteignent jamais Production. Les branches sont automatiquement poussées vers l'aperçu ; fusionner vers main se déploie vers Production.

Étape 2 : CSP strict via un middleware

Générez un nom occasionnel par requête, puis injectez-le dans Content-Security-Policy. Next.js applique automatiquement le nom occasionnel à ses propres balises de script lorsque vous définissez l'en-tête de requête x-nonce.

ts
// 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).*)'],
};

Étape 3 : Forcer RLS sur chaque table publique

RLS isn't enabled by default on tables you create in SQL or migrations. Enable it on every exposed table and pair each one with explicit policies per role — that is what stops the anon and authenticated roles. FORCE only makes the table owner obey RLS too.

sql
-- 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);

Étape 4 : Vérification d'authentification sur le serveur uniquement sur chaque route API

Chaque route API qui change d'état vérifie le côté serveur de l'appelant avec supabase.auth.getUser(). L'objet utilisateur devient la source de vérité pour user_id — ne faites jamais confiance à un corps de requête pour le définir.

ts
// 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);
}

Étape 5 : Effectuez un proxy inverse pour vos analyses

Proxying analytique via votre propre domaine évite les bloqueurs de publicités et permet à votre CSP connect-src 'self' de rester étroit. Le même modèle fonctionne pour PostHog, Plausible, Umami et les récepteurs d'événements personnalisés.

ts
// 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(),
  });
}

Étape 6 : Garde de redirection ouverte sur le rebond post-authentification

Les flux de connexion/inscription acceptent généralement un paramètre de requête next. Rejetez tout ce qui n'est pas un chemin sur le même site : commencez par / et jamais // (relatif au protocole, envoie les utilisateurs hors site).

ts
function safeNext(raw: string | null): string {
  if (!raw) return '/dashboard';
  if (!raw.startsWith('/') || raw.startsWith('//')) return '/dashboard';
  return raw;
}

En cours : surveillance et nouvelle analyse

La dérive se produit à chaque déploiement. Traitez la sécurité comme une boucle et non comme une liste de contrôle à terminer.

Vérifiez votre domaine de production

Dashboard → Domains → add your production domain → DNS TXT or HTTP-file verification. Active scans require Hobby or above; scheduled re-scans require Pro or Unlimited.

Planifier de nouvelles analyses passives

Scheduled re-scans are available on Pro and Unlimited for verified domains. Free and Hobby scans are manual. Scheduled scans use your plan allowance. Configure completion email preferences and a scan.completed webhook if needed.

bash
# 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"}'

Activer API-analyses actives (facultatif)

Si vous souhaitez une détection active automatisée (SQLi / XSS / IDOR marche / etc.), activez-la par domaine dans Tableau de bord → Domaines → API actif. L'autorisation est durable, expire 90 jours, instantanément révocable. Associez-le au webhook scan.active_api.first_used pour que la première analyse active automatisée après l'activation atteigne votre alerte.

Intégrez les résultats à votre flux de travail AI

On Hobby or above, create an API token at Account → API tokens and configure the MCP server (/docs/mcp) in your coding tool. Ask your agent to run an authorized scan and inspect the highest-severity findings. Code fixes can use remediation prompts; provider and DNS fixes may need manual operator steps.

Détection des menaces en direct (Unlimited)

Periodic certificate-transparency, DNS, JS-bundle, and threat-intelligence checks report observed changes on supported signals. Alerts depend on successful polling and source availability; they do not establish continuous or complete security coverage.

Modèles de défaillance réels et leurs correctifs

Five common patterns in AI-generated apps, each with the actual fix:

  1. Clé de rôle de service dans un composant client

    Symptom: FixVibe reports an exposed Supabase service-role key on the production URL. Cause: an autocomplete pasted createClient(URL, SERVICE_ROLE_KEY) into a React component. Fix: move the service client to src/lib/supabase/service.ts with import 'server-only' at the top; create a parallel src/lib/supabase/client.ts using the anon key for client-side use; rotate the service-role key via Supabase Studio.

  2. Règles Firestore laissées en mode test

    Symptom: a high-severity open Firebase rules finding. Cause: generated rules read allow read, write: if request.time < timestamp.date(2026, 6, 1); — a time-bounded "allow all". Fix: scope each rule to the authenticated user — match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; } — and re-deploy firebase deploy --only firestore:rules.

  3. Permissif CORS survivant en production

    Symptom: a high-severity CORS misconfiguration finding. Cause: generated Express middleware: app.use(cors({ origin: '*' })). Fix: allowlist your frontend origin: app.use(cors({ origin: ['https://your-app.com'], credentials: true })). For Next.js API routes, set Access-Control-Allow-Origin explicitly in the response.

  4. RLS activé mais pas forcé

    Symptom: FixVibe reports that anonymous visitors can read a public table even though RLS looks enabled in the dashboard. Cause: RLS is on, but a policy such as USING (true) lets the anon role through, or the migration that tightened it never ran in production. Fix: replace the permissive policy with one scoped to auth.uid(), apply the migration, and re-scan.

  5. IDOR-ID accessibles à pied non signés

    Symptom: an active scan on your verified domain reports that one user can read another user's records at /api/items/1, /api/items/2, ... Cause: the API handler trusts the path param and queries Postgres without an ownership predicate. Fix: add .eq('user_id', user.id) on every read query, or move to signed URLs / UUIDs scoped under /api/users/[uid]/items/[id].

La boucle de sécurité du vibe-code

L'objectif n'est pas une sécurité parfaite ; cela élimine les fruits à portée de main AI que les outils manquent constamment afin que vous puissiez continuer à expédier rapidement.

  1. Generate fast — utilisez Cursor, Claude Code, Lovable, Bolt. C'est le point.
  2. Audit immediately — exécutez l'ensemble grep ci-dessus, vérifiez RLS, vérifiez CSP, vérifiez la limite d'authentification.
  3. Harden at deploy — middleware, séparation de l'environnement, CSP nonce, HSTS, vérification d'authentification sur le serveur uniquement.
  4. Monitor — FixVibe passif quotidiennement, actif chaque semaine sur un domaine vérifié, webhooks vers Slack, détection des menaces sur Unlimited.
  5. 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.

Prochaines étapes

Pour obtenir le contexte conceptuel sur DAST vs SAST et pourquoi AI-les applications générées ont besoin de leur propre analyse, lisez AI-generated code security scanning. Pour un audit de référence rapide avant expédition, consultez le vibe coding security checklist.

// scanner votre app

Arrêtez de lire. Trouvez les failles dans la vôtre.

Drop in a URL — FixVibe runs every passive check from this guide plus the rest of its 230+ passive checks, usually in under a minute. Free, no install, no card.

  • Plan gratuit — 3 scans / mois, sans carte.
  • Scans passifs contre n'importe quelle URL — pas de vérification de domaine.
  • Optimisé pour Cursor, Claude Code, Lovable, Bolt, v0, Replit.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
Comment sécuriser une app construite avec des outils de codage IA · FixVibe