FixVibe

// docs / security guides / hardening

Cómo asegurar una app creada con herramientas de IA

Guía de hardening paso a paso para apps que creaste con Cursor, Claude Code, Lovable, Bolt, v0, Replit o Windsurf. Cuatro fases: entender por qué las apps generadas por IA fallan distinto, ejecutar una auditoría inmediata del código, hacer hardening en el deploy y mantener el monitoreo. Opinionada, narrativa, con fragmentos reales que puedes copiar.

Por qué las aplicaciones generadas por AI- fallan de manera diferente

Las aplicaciones codificadas por Vibe pueden ser seguras. Necesitan un pase de auditoría adicional porque los modos de falla son estructurales, no descuidados:

  • Cursor inlines hardcoded keys. Le pide a Cursor que "arregle el error de autenticación" y pega un ejemplo de Supabase que asume un cliente con función de servicio. La clave termina en la parte superior de un componente de la página. Tanto el cliente anon como el cliente de servicio coexisten; ambos barcos.
  • Claude Code defaults to permissive CORS. Los controladores Generated Express / Fastify se envían con cors({ origin: '*' }) porque es la forma más rápida de obtener una vista previa funcional. El middleware nunca recibe una segunda pasada.
  • Lovable and v0 skip the rules file. Los proyectos respaldados por Firestore generan el modelo de datos pero rara vez tocan firestore.rules. Las reglas del modo de prueba expiran silenciosamente y bloquean la base de datos sin previo aviso al usuario.
  • Bolt skips RLS migrations. Bolt genera un esquema Supabase y una superficie CRUD que utiliza la clave anon. ENABLE ROW LEVEL SECURITY nunca entra en la migración. Los usuarios anónimos pueden leer o escribir cualquier fila.
  • Windsurf trusts unsigned IDs. Generado GET /api/items/[id] lee el parámetro y consulta Postgres sin verificar la propiedad. El patrón es lo suficientemente común como para que la sonda active.idor-walking activa lo muestre en un solo escaneo.

La auditoría inmediata: busque en su código base patrones de riesgo

Antes de endurecer algo, encuentra lo que ya está roto. Cada uno de estos greps tarda menos de un minuto:

Secretos y claves de proveedor

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

Cualquier hit debe eliminarse y rotarse la clave. Provider paneles de control: Supabase → Configuración → API, Stripe → Desarrolladores → API claves, consola Anthropic / OpenAI.

Controles de acceso a la base de datos

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

Cada CREATE TABLE public.* necesita un ENABLE ROW LEVEL SECURITY coincidente y al menos una política. Las reglas de Firestore deben limitar las lecturas a request.auth.uid.

Manejo de autenticación y sesión

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/

Las rutas renderizadas por el servidor deben usar supabase.auth.getUser(); se verifica con el backend. getSession() lee una cookie no verificada. Los tokens en localStorage son accesibles para cualquier script que se ejecute en la página.

Encabezados y 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/

Con el diseño src/, solo se selecciona src/middleware.ts. Si su archivo de middleware está en la raíz del proyecto, Next.js lo ignora silenciosamente y su lógica CSP/auth-refresh nunca se ejecuta.

Endurecimiento en el momento de la implementación

Una vez que la fuente esté limpia, bloquee cómo la aplicación llega a producción.

Paso 1: ambientes separados

Vercel: tres entornos: Producción (su dominio de producción), Vista previa (PR / implementaciones provisionales), Desarrollo (local). Cada uno tiene su propio conjunto env-var. Las teclas Live Stripe / Anthropic / Supabase nunca llegan a la Vista previa; Las claves de vista previa nunca llegan a Producción. Las ramas pasan a Vista previa automáticamente; fusionarse con main se implementa en Producción.

Paso 2: Estricto CSP mediante middleware

Genere un nonce por solicitud y luego inyéctelo en Content-Security-Policy. Next.js aplica automáticamente el nonce a sus propias etiquetas de script cuando configura el encabezado de solicitud 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).*)'],
};

Paso 3: Fuerza RLS en cada mesa pública

RLS no está habilitado de forma predeterminada y no se aplica a los propietarios de tablas a menos que lo FORCE lo haga. Empareje cada tabla con políticas explícitas por rol.

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

Paso 4: Verificación de autenticación solo del servidor en cada ruta API

Cada ruta API que cambia de estado verifica el lado del servidor de la persona que llama con supabase.auth.getUser(). El objeto de usuario se convierte en la fuente de verdad para user_id; nunca confíe en el cuerpo de una solicitud para configurarlo.

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

Paso 5: realice un proxy inverso de sus análisis

Proxying análisis a través de su propio dominio evita bloqueadores de anuncios y permite que su CSP connect-src 'self' se mantenga limitado. El mismo patrón funciona para PostHog, Plausible, Umami y receptores de eventos personalizados.

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

Paso 6: guardia de redireccionamiento abierto en el rebote posterior a la autenticación

Los flujos de inicio de sesión/registro suelen aceptar un parámetro de consulta next. Rechace todo lo que no sea una ruta del mismo sitio: comience con / y nunca con // (relativo al protocolo, envía a los usuarios fuera del sitio).

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

En curso: monitoreo y reescaneo

La deriva ocurre en cada despliegue. Trate la seguridad como un bucle, no como una lista de verificación que debe completar.

Verifique su dominio de producción

Dashboard → Domains → agregue su dominio de producción → DNS TXT o HTTP-verificación de archivos (en un solo paso). Una vez verificados, los análisis activos estarán disponibles y se podrán habilitar los nuevos análisis programados.

Programar nuevos análisis pasivos

Diariamente el Hobby, cada 3 horas el Pro, cada hora el Unlimited. Cada ejecución le envía un correo electrónico si aparece un nuevo hallazgo y activa un webhook scan.completed si se ha suscrito.

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

Habilitar API-análisis activos (opcional)

Si desea un sondeo activo automatizado (SQLi / XSS / IDOR caminando / etc.), actívelo por dominio en Panel → Dominios → API activo. La autorización es duradera, tiene un vencimiento de 90 días y es revocable instantáneamente. Emparéjelo con el webhook scan.active_api.first_used para que el primer escaneo activo automatizado después de la habilitación llegue a su alerta.

Conecte los hallazgos a su flujo de trabajo AI

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.

Detección de amenazas en vivo (Unlimited)

Las diferencias del registro de transparencia de certificados muestran nuevos certificados TLS emitidos para su dominio. DNS-record diffs detecta cambios no autorizados. JS-el monitoreo secreto del paquete se activa en el momento en que una nueva clave llega al paquete enviado. Las fuentes de información sobre amenazas (Spamhaus, URLhaus) informan sobre su dominio si está en la lista.

Patrones de fallas reales y sus soluciones.

Cinco patrones de escaneos de producción en miles de aplicaciones generadas por AI-, cada una con la solución real:

  1. Clave de función de servicio en un componente de cliente

    Symptom: baas.supabase-service-key encontrando en la producción URL. Cause: a Cursor autocompletar pegado createClient(URL, SERVICE_ROLE_KEY) en un componente de React. Fix: mueve el cliente de servicio a src/lib/supabase/service.ts con import 'server-only' en la parte superior; cree un src/lib/supabase/client.ts paralelo usando la clave anónima para uso del lado del cliente; gire la clave de función de servicio a través de Supabase Studio.

  2. Reglas de Firestore dejadas en modo de prueba

    Symptom: baas.firebase-rules hallazgo de alta gravedad. Las reglas generadas por Cause: leen allow read, write: if request.time < timestamp.date(2026, 6, 1);: un "permitir todo" con un límite de tiempo. Fix: aplica cada regla al usuario autenticado (match /users/{userId}/posts/{postId} { allow read, write: if request.auth.uid == userId; }) y vuelve a implementar firebase deploy --only firestore:rules.

  3. Permisivo CORS sobreviviendo en producción

    Symptom: active.cors de alta gravedad. Cause: middleware Express generado: app.use(cors({ origin: '*' })). Fix: incluye en la lista permitida el origen de tu interfaz: app.use(cors({ origin: ['https://your-app.com'], credentials: true })). Para rutas Next.js API, establezca Access-Control-Allow-Origin explícitamente en la respuesta.

  4. RLS habilitado pero no forzado

    Symptom: activo baas.supabase-rls informa que la función anónima puede escribir en una tabla pública aunque RLS esté habilitado en el panel. Cause: ENABLE sin FORCE deja al propietario de la tabla exento y las migraciones se ejecutan como propietario. Fix: agrega alter table public.items force row level security; a la migración. Volver a implementar.

  5. Identificaciones transitables IDOR- sin firmar

    Symptom: active.idor-walking informa que el usuario anónimo puede leer /api/items/1, /api/items/2, ... entre inquilinos. Cause: el controlador API confía en el parámetro de ruta y consulta Postgres sin un predicado de propiedad. Fix: agregue .eq('user_id', user.id) en cada consulta de lectura, o muévase a URL/UUID firmados con alcance en /api/users/[uid]/items/[id].

El bucle de seguridad del código de vibración

El objetivo no es la seguridad perfecta; está eliminando la fruta más fácil que las herramientas AI pasan por alto constantemente para que puedas seguir realizando envíos rápidos.

  1. Generate fast — utilice Cursor, Claude Code, Lovable, Bolt. Ese es el punto.
  2. Audit immediately: ejecute el grep configurado anteriormente, verifique RLS, verifique CSP, revise el límite de autenticación.
  3. Harden at deploy: middleware, separación de entornos, CSP nonce, HSTS, verificación de autenticación solo del servidor.
  4. Monitor — FixVibe pasivo diario, activo semanalmente en un dominio verificado, webhooks para Slack, detección de amenazas en 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.

Próximos pasos

Para conocer el contexto conceptual sobre DAST frente a SAST y por qué las aplicaciones generadas por AI- necesitan su propio escaneo, lea AI-generated code security scanning. Para obtener una referencia rápida sobre la auditoría previa al envío, consulte vibe coding security checklist.

// escanea tu app

Deja de leer. Empieza a encontrar las brechas en la tuya.

Pega una URL — FixVibe ejecuta todas las verificaciones pasivas de esta guía más 200 adicionales en menos de un minuto. Gratis, sin instalación, sin tarjeta.

  • Tier gratis — 3 escaneos / mes, sin tarjeta.
  • Escaneos pasivos contra cualquier URL — sin verificación de dominio.
  • Afinado para Cursor, Claude Code, Lovable, Bolt, v0, Replit.
  • Coding-agent prompts for code/config findings, plus operator steps for DNS/provider fixes.
Cómo asegurar una app creada con herramientas de IA — Docs · FixVibe