Saltar al contenido

Los 12 errores de seguridad más comunes en SaaS vibe-coded

Las 12 vulnerabilidades más comunes en SaaS hechos con Lovable, Bolt, v0 y Cursor. Cada error con ejemplo vulnerable, ejemplo seguro y cómo detectarlo.

Seguridad Equipo VibeAuditt 9 min de lectura

Los SaaS construidos con asistentes de IA — Lovable, Bolt, v0, Cursor, Claude Code — comparten un patrón: el código funciona, se ve bien y pasa los tests, pero las decisiones de seguridad por defecto son frágiles. Estos son los 12 errores que más detectamos en VibeAuditt, con ejemplos reales (anonimizados) de lo vulnerable y lo seguro.

TL;DR

  • El problema está medido: en un escaneo de 1.072 apps vibe-coded sobre Supabase, Symbiotic Security (2026) encontró que el 98% tenía al menos un fallo y el 16% uno crítico. El caso de RLS insuficiente en Lovable está formalizado como CVE-2025-48757 (CVSS 9.3).
  • Secretos en el bundle: claves de Stripe, OpenAI, Supabase service role y JWTs filtrados en JS público — el #1 con diferencia.
  • Supabase y Firebase sin reglas: bases de datos accesibles desde cualquier navegador con la anon key.
  • Headers de seguridad ausentes: CSP, HSTS, X-Frame-Options son la línea de defensa contra XSS, clickjacking y MITM.
  • Configuración mal calibrada: CORS permisivo, .git expuesto, console logs con tokens, source maps en producción.

Por qué los SaaS vibe-coded tienen estos problemas

Cuando le pedís a un modelo de IA “hacé un dashboard que use Supabase”, el modelo prioriza que funcione. La política RLS, los headers HTTP, el CORS restrictivo y la validación servidor son detalles que no salen al menos que los pidas explícitamente. El happy path domina el output.

Además, la mayoría de tutoriales que esos modelos consumieron durante entrenamiento son demos de YouTube y posts de blog optimizados para “ver el resultado rápido”. Pocos cubren seguridad antes de hablar de UI.

El resultado: SaaS que funcionan perfecto en demo y se rompen en producción cuando alguien con curiosidad abre DevTools.

Categoría 1 — Secretos expuestos en código cliente

1. API keys en el bundle JavaScript

El error #1 con diferencia. Cualquier clave que usa el frontend para llamar a un servicio termina en el bundle público. Si la clave puede hacer operaciones privilegiadas (cobrar a un usuario, escribir en la base de datos, generar embeddings pagos), tenés un problema.

Vulnerable:

// src/lib/openai.ts — en el bundle público
import OpenAI from 'openai';
export const openai = new OpenAI({
  apiKey: 'sk-proj-AAAA...BBBB', // ❌ visible para cualquiera
});

Seguro:

// Mover a un endpoint backend
// apps/api/src/routes/embeddings.ts
export async function POST(req: Request) {
  const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY }); // server only
  const { text } = await req.json();
  // validar usuario, rate limit, etc.
  return Response.json(await openai.embeddings.create({ input: text }));
}

Llamás al endpoint desde el frontend con la sesión del usuario, nunca con la API key.

Cómo detectar: buscá sk_live_, sk-proj-, eyJ (JWT) en los archivos .js de tu carpeta dist/. VibeAuditt corre 30+ patrones de claves conocidas (Stripe, OpenAI, AWS, SendGrid, Twilio, Resend, Anthropic, Linear, Figma, etc.).

2. Archivos de configuración expuestos

El segundo error más destructivo: dejar .env, wp-config.php, config.json o backups SQL accesibles vía URL. Lovable o v0 no los suelen exponer si usás el deploy default, pero la migración manual a Vercel/Netlify a veces incluye estos archivos en el output.

Vulnerable: https://mi-app.com/.env devuelve 200 OK con DATABASE_URL=postgresql://...

Seguro:

  • Asegurate que tu deploy ignora archivos dotfile (Vercel y Netlify lo hacen por default)
  • Tu .gitignore debe incluir .env*, *.sql, *.bak
  • Cuando hagas vercel deploy, revisá qué archivos sube el adapter

Cómo detectar: probá cargar /.env, /.env.local, /wp-config.php, /config.json, /backup.sql en tu navegador. Si te devuelve algo distinto a 404, hay problema.

3. Directorio .git expuesto

Si subiste tu app a un servidor sin ignorar .git/, todo tu historial está disponible. Con herramientas como git-dumper un atacante reconstruye el repo completo, incluyendo commits viejos con claves antes de que las rotaras.

Vulnerable: GET /.git/HEAD retorna ref: refs/heads/main

Seguro: configurar tu servidor (nginx, Cloudflare Pages, Vercel) para que devuelva 404 en /.git/. La mayoría de PaaS modernas lo hacen por default, pero VPS manuales son vulnerables a esto.

Categoría 2 — Bases de datos accesibles desde el browser

4. Supabase sin Row Level Security (el error #1 en stacks con Supabase)

Supabase expone tu Postgres vía REST con una anon key pública. Si las tablas no tienen RLS habilitado, esa anon key permite leer (y a veces escribir) cualquier dato.

Vulnerable:

-- Sin políticas, con RLS desactivado
CREATE TABLE users (id uuid, email text, stripe_id text, plan text);
ALTER TABLE users DISABLE ROW LEVEL SECURITY; -- ❌

Con esto, un visitante de tu landing puede ir a la consola y correr:

const sb = window.supabase ?? createClient(url, anonKey);
const { data } = await sb.from('users').select('*');
console.log(data); // todo el SaaS expuesto

Seguro:

ALTER TABLE users ENABLE ROW LEVEL SECURITY;

CREATE POLICY "users select own"
  ON users FOR SELECT
  USING (auth.uid() = id);

CREATE POLICY "users update own"
  ON users FOR UPDATE
  USING (auth.uid() = id);

Cubrimos este error en detalle en nuestro post sobre Supabase RLS.

5. Firebase con reglas allow-all

El equivalente Firebase: dejar las reglas de Firestore o Realtime DB como allow read, write: if true. El SDK de Firebase incluye apiKey y projectId en el frontend; sin reglas, esos datos permiten acceso total.

Vulnerable (firestore.rules):

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true; // ❌ catastrófico
    }
  }
}

Seguro:

match /users/{userId} {
  allow read: if request.auth != null && request.auth.uid == userId;
  allow write: if request.auth != null && request.auth.uid == userId;
}

6. Endpoints API sin autenticación

Rutas como /api/users, /api/admin, /api/internal/stats que devuelven datos sensibles sin pedir token. Es común cuando se prototipa un endpoint para tests y se olvida agregar el guard antes del deploy.

Vulnerable: GET /api/users retorna todos los usuarios con email y plan.

Seguro: cada endpoint API debe pasar por un middleware de autenticación. En Hono, Express, Next API routes:

app.use('/api/*', authMiddleware); // antes de los handlers
app.get('/api/users', listUsersHandler);

Y dentro del handler, verificar que el usuario tiene permiso para ver lo que pide.

Categoría 3 — Headers HTTP de seguridad

7. Content Security Policy (CSP) ausente

Sin CSP, cualquier script inyectado en tu página vía XSS puede hacer fetch a un servidor del atacante, robar tokens, modificar el DOM. CSP le dice al browser “solo cargá scripts de estos orígenes y nada de inline”.

Vulnerable: tu response carece de Content-Security-Policy.

Seguro:

Content-Security-Policy: default-src 'self';
  script-src 'self' https://js.stripe.com 'sha256-...';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https:;
  connect-src 'self' https://api.openai.com;
  frame-ancestors 'none';
  base-uri 'self';
  form-action 'self';

Profundizamos esto en security headers HTTP.

8. HSTS sin preload

Si tu app sirve sobre HTTPS pero el visitante llega vía http://tu-app.com, hay una ventana antes del redirect 301 donde un MITM puede hacer downgrade. HSTS con preload elimina esa ventana.

Seguro:

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Y registrar el dominio en hstspreload.org (proceso una sola vez).

9. X-Frame-Options / frame-ancestors ausente

Sin este header, alguien puede embeberte en un iframe en un sitio malicioso, superponer divs invisibles y secuestrar clicks del usuario logueado. Es el ataque clásico de clickjacking.

Seguro:

X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'

(El segundo es la versión moderna; ambos no estorban.)

Categoría 4 — Configuración débil

10. CORS mal configurado

La combinación más peligrosa: Access-Control-Allow-Origin: * con Access-Control-Allow-Credentials: true. El browser permitirá que cualquier sitio haga requests con cookies del usuario logueado.

Vulnerable:

res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Credentials', 'true'); // ❌

Seguro:

const ALLOWED_ORIGINS = ['https://tu-app.com', 'https://www.tu-app.com'];
const origin = req.headers.origin;
if (ALLOWED_ORIGINS.includes(origin)) {
  res.setHeader('Access-Control-Allow-Origin', origin);
  res.setHeader('Access-Control-Allow-Credentials', 'true');
}

11. Source maps en producción

Los .js.map permiten reconstruir el código fuente original con nombres de variables, comentarios y estructura interna. Útil en dev, regalo para atacantes en producción.

Vulnerable: dist/index.abc123.js.map accesible y referenciado en //# sourceMappingURL=....

Seguro: configurar tu bundler para no emitir sourcemaps en producción, o emitirlos a un servicio privado (Sentry) y borrarlos del deploy público.

// vite.config.ts
export default {
  build: { sourcemap: false }, // o 'hidden' si los usás con Sentry
};

12. Console disclosure (logs sensibles en producción)

console.log("user JWT:", token) durante debugging que sobrevivió al deploy. Cualquier visitante abre DevTools, ve el JWT, y si tu sesión es larga, la usa.

Vulnerable:

const session = await getSession();
console.log('session:', session); // ❌ JWT, refresh token, user_id

Seguro: configurar tu bundler para stripear console.log en producción.

// vite.config.ts
export default {
  esbuild: { drop: ['console', 'debugger'] },
};

VibeAuditt renderiza tu app en un browser headless y captura los console.log que aparecen — si hay tokens o info sensible, los reporta.

Cómo auditar tu propio SaaS

Cubrimos los 12 errores con auditorías automáticas en VibeAuditt. Tres auditorías gratis al mes, sin tarjeta. Si querés revisarlos manualmente:

  1. Abrí DevTools → Network. Cargá tu app y buscá requests a APIs externas. Cada una con Authorization en el header del request o con claves en el body → revisar.
  2. Sources tab. Buscá strings como sk_, pk_, eyJ, service_role, secret. Si aparecen, mover a backend.
  3. Headers de respuesta. Cargá tu página y mirá los headers en la pestaña Headers del Network. Falta CSP, HSTS, X-Frame-Options? Agregalos.
  4. Pruebas manuales. Sin estar logueado, abrí la consola y probá hacer query a tu Supabase o Firebase con la anon key del bundle. Si responde con datos, RLS está mal.

Si querés un reporte detallado en español con severidad por finding y recomendación concreta, podés generarlo en menos de un minuto en VibeAuditt.

Para guías específicas por stack, revisá nuestras páginas:

// preguntas frecuentes

Preguntas frecuentes

¿Por qué los SaaS hechos con IA tienen más vulnerabilidades?

Los modelos generativos optimizan para que el código compile y funcione, no para que sea seguro. El happy path domina el contexto y patrones defensivos como CSP, RLS o validación servidor quedan fuera del scope típico del prompt. Es un problema de prioridades del entrenamiento, no de capacidad del modelo.

¿Es suficiente con auditar mi SaaS antes del launch?

No. Cada vez que actualizás dependencias, cambiás integraciones o agregás features, podés introducir nuevas vulnerabilidades. Recomendamos auditar antes de lanzar, después de cada release mayor y mínimo una vez al mes. El plan Pro de VibeAuditt incluye monitoring automático semanal.

¿VibeAuditt explota las vulnerabilidades que detecta?

Nunca. Nuestro scanner solo hace reconocimiento pasivo: descarga HTML público, lee bundles JavaScript expuestos y prueba endpoints documentados como públicos (anon keys de Supabase, configuración Firebase). No ejecutamos exploits, no hacemos fuerza bruta y respetamos robots.txt.

Si arreglo estos 12 errores, ¿mi SaaS queda 100% seguro?

No existe seguridad 100%. Cubrir los 12 errores te aleja del 80-90% de los ataques automatizados que escanean internet buscando configuraciones débiles, pero no te protege contra ataques dirigidos, vulnerabilidades en dependencias o errores lógicos en tu propio código. Es la base, no el techo.

¿Qué error es el más crítico de los 12?

Depende del stack, pero los más destructivos suelen ser API keys expuestas (con la clave de Stripe en el bundle alguien puede cobrar a tus usuarios) y Supabase sin RLS (un atacante con tu anon key puede leer toda la base de datos). Ambos son triviales de explotar y de impacto inmediato.

// escanear ahora

Audita tu SaaS gratis

Recibí un reporte de seguridad en menos de un minuto. Detectamos claves expuestas, RLS deshabilitado, headers de seguridad ausentes y más. Sin tarjeta, sin instalación.

Empezar auditoría

10 auditorías gratis al mes · reportes detallados desde 9 USD