Cómo asegurar tu app de Lovable, Bolt o v0 antes de lanzar
Checklist práctico de seguridad para apps generadas con Lovable, Bolt o v0. Variables expuestas, RLS, API keys, headers. Antes de hacer público, hacé esto.
Lovable, Bolt y v0 generan apps que funcionan en minutos. Pero “funciona” y “es seguro lanzarla” son dos cosas distintas. Este es el checklist que aplicamos antes de cada launch.
TL;DR
- Por qué importa este checklist: Escape.tech (2025) analizó 5.600 apps vibe-coded y halló más de 2.000 vulnerabilidades, 400+ secretos expuestos y 175 casos de datos personales. Casi todo eso se previene antes de lanzar.
- Buscá claves en el bundle y movelas a backend
- Habilitá Row Level Security en Supabase para cada tabla
- Configurá headers de seguridad (CSP, HSTS, X-Frame-Options)
- Revisá CORS, source maps, console.log en producción
- Validá inputs en servidor, nunca solo en cliente
- Audita el resultado con una herramienta automatizada
Paso 1 — Audita el bundle JavaScript
El error más destructivo y más común: claves de servicios pagos terminan en el código que descarga el browser.
Después de hacer build (npm run build o equivalente), abrí los archivos .js de la carpeta dist/ o .output/ y buscá:
# Patterns más comunes
grep -r "sk_live_" dist/ # Stripe live key
grep -r "sk-proj-" dist/ # OpenAI project key
grep -r "service_role" dist/ # Supabase service role
grep -r "eyJ" dist/ # JWTs (los reales empiezan así)
grep -r "AKIA" dist/ # AWS access key
grep -r "ghp_" dist/ # GitHub personal access token
grep -r "xoxb-" dist/ # Slack bot token
Si aparecen, rotá la clave inmediatamente en el dashboard del servicio y mové la lógica que la usa a un endpoint backend.
Convención de nombres por builder
Cada framework tiene su prefijo para variables que SÍ se exponen al cliente:
- Vite / Lovable: solo las que empiezan con
VITE_*van al bundle - Next.js (v0): solo
NEXT_PUBLIC_* - Bolt / SvelteKit: solo
PUBLIC_* - Astro: solo
PUBLIC_*
Cualquier variable sin esos prefijos queda server-side. Si necesitás que el frontend acceda a una clave (Stripe publishable key, Supabase anon key, Google Maps), usá el prefijo correcto. Si la clave es secreta, nunca le pongas el prefijo público.
Vulnerable (.env):
VITE_SUPABASE_SERVICE_ROLE_KEY=eyJ... # ❌ "VITE_" expone la service role
VITE_STRIPE_SECRET=sk_live_... # ❌ se va al bundle
VITE_OPENAI_KEY=sk-proj-... # ❌ idem
Seguro:
# Cliente (van al bundle)
VITE_SUPABASE_URL=https://xxx.supabase.co
VITE_SUPABASE_ANON_KEY=eyJ...
VITE_STRIPE_PUBLISHABLE_KEY=pk_live_...
# Servidor (NO van al bundle, usar en endpoints backend)
SUPABASE_SERVICE_ROLE_KEY=eyJ...
STRIPE_SECRET_KEY=sk_live_...
OPENAI_API_KEY=sk-proj-...
Paso 2 — Row Level Security en Supabase
Si tu app usa Supabase (caso de >80% de los proyectos Lovable/Bolt), este paso es el más importante. La anon key está en el bundle por diseño; lo que protege tus datos son las políticas RLS.
Activá RLS en todas las tablas
Por cada tabla en tu schema, en el SQL editor de Supabase:
ALTER TABLE nombre_tabla ENABLE ROW LEVEL SECURITY;
Si no creás ninguna política después, la tabla queda invisible para clientes anónimos y autenticados. Eso es seguro por default (deny-all).
Agregá políticas explícitas
Para que tu app funcione, necesitás permitir lo justo:
-- Ejemplo: tabla "profiles"
CREATE POLICY "Los usuarios ven solo su perfil"
ON profiles FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Los usuarios actualizan solo su perfil"
ON profiles FOR UPDATE
USING (auth.uid() = user_id);
CREATE POLICY "Los usuarios crean solo su propio perfil"
ON profiles FOR INSERT
WITH CHECK (auth.uid() = user_id);
Evitá políticas peligrosas
Estas políticas son equivalentes a no tener RLS:
-- ❌ Permite todo a cualquiera
CREATE POLICY "open" ON tabla FOR ALL USING (true);
-- ❌ Permite a cualquier autenticado, sin distinción
CREATE POLICY "auth_open" ON tabla FOR ALL
USING (auth.role() = 'authenticated');
La segunda es trampa: cualquiera puede crearse cuenta en tu Supabase con signUp, lo que vuelve authenticated la condición trivial.
Más detalle en nuestro post de Supabase RLS.
Paso 3 — Headers de seguridad HTTP
Por default, ni Lovable ni Bolt ni v0 configuran headers de seguridad. Tu hosting (Cloudflare Pages, Vercel, Netlify) los puede agregar con un archivo de configuración.
Cloudflare Pages
Creá public/_headers:
/*
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://*.supabase.co https://api.stripe.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Vercel
En vercel.json:
{
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "Content-Security-Policy", "value": "default-src 'self'; ..." },
{ "key": "Strict-Transport-Security", "value": "max-age=63072000; includeSubDomains; preload" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
]
}
]
}
Netlify
En netlify.toml:
[[headers]]
for = "/*"
[headers.values]
Content-Security-Policy = "default-src 'self'; ..."
Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload"
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
CSP es el más delicado. Empezá con una política amplia ('unsafe-inline' en script-src) y andá ajustando viendo qué bloquea en consola. Reportá errores con report-uri o report-to y leelos en producción.
Más detalle en security headers HTTP.
Paso 4 — Configuración del build
Stripeá console.log y debugger en producción
Si usás Vite (Lovable, Bolt):
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
esbuild: {
drop: process.env.NODE_ENV === 'production' ? ['console', 'debugger'] : [],
},
});
Para Next.js (v0):
// next.config.js
module.exports = {
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
};
Desactivá source maps públicos
Source maps en producción dejan tu código original accesible. O los sacás, o los enviás a un servicio privado (Sentry):
// vite.config.ts
export default defineConfig({
build: { sourcemap: false }, // o 'hidden' para uploadear a Sentry y no publicar
});
// next.config.js
module.exports = { productionBrowserSourceMaps: false };
Verificá que no se sirve .env ni .git
Después del build, antes de deployar, probá en local:
npx serve dist
# o
npx vercel dev
Navegá a http://localhost:3000/.env, /.env.local, /.git/HEAD. Deberían dar 404. Si dan otra cosa, hay archivos que no querés exponer en tu dist/.
Paso 5 — Validación de inputs
Lovable, Bolt y v0 generan código que valida en cliente (required, pattern, etc.) pero rara vez valida en servidor. Cualquier usuario con curl evita la validación cliente.
Vulnerable (solo cliente):
// Solo cliente — válido pero insuficiente
<input type="email" required pattern="..." />
Seguro (cliente + servidor):
// Cliente: UX
<input type="email" required pattern="..." />
// Servidor (Hono, Next API, etc.) con Zod
import { z } from 'zod';
const Schema = z.object({
email: z.string().email().max(255),
message: z.string().min(1).max(5000),
});
app.post('/api/contact', async (c) => {
const data = Schema.parse(await c.req.json()); // throws 400 si inválido
// ...
});
CSRF en endpoints de mutación
Si usás cookies para autenticación, cada POST/PUT/DELETE que cambia estado necesita protección CSRF. Las opciones más simples:
SameSite=LaxoSameSite=Stricten la cookie de sesión (defensa principal)- Header personalizado (e.g.
X-Requested-With) que el frontend manda y el server verifica - Token CSRF rotado por sesión
Si tu API solo acepta JWTs en el header Authorization, no necesitás CSRF (los headers customizados no se envían cross-origin).
Paso 6 — Pre-launch checklist final
Antes de hacer público, corré este checklist:
- Bundle JS no contiene
sk_live_,sk-proj-,service_role,eyJ,AKIA,ghp_, ni similares - Todas las tablas Supabase tienen RLS habilitado con políticas explícitas
- Probaste consultas Supabase sin estar logueado desde la consola y devuelve solo datos públicos (o nada)
- Headers configurados: CSP, HSTS preload, X-Frame-Options, X-Content-Type-Options, Referrer-Policy
- CORS no es
*concredentials: true - Source maps no se sirven en producción
-
console.logydebuggerremovidos en build de producción -
/.env,/.git/HEADretornan 404 - Inputs validados en servidor con Zod o similar
- Endpoints API verifican autenticación antes de devolver datos
- Auditaste el resultado con VibeAuditt para tener un segundo par de ojos automatizado
Si el último punto te falla, mejor descubrirlo ahora que cuando alguien reporta un breach en Twitter. La auditoría gratis incluye los 12 checks que cubren los errores arriba; el reporte detallado en PDF (plan Pro) suma severidad por finding y recomendación concreta.
Para deep-dive específico por stack, revisá Lovable, Bolt, v0, Cursor y Supabase.
// preguntas frecuentes
Preguntas frecuentes
¿Es seguro publicar una app de Lovable o Bolt sin ajustes adicionales?
Sin ajustes, no. Estos builders priorizan velocidad de iteración sobre seguridad por defecto. Sin revisar variables de entorno, RLS de Supabase, headers de seguridad y exposición de claves en el bundle, vas a estar publicando una app con vulnerabilidades triviales de explotar.
¿Cuánto tiempo lleva asegurar una app vibe-coded antes del launch?
Entre 2 y 6 horas si seguís este checklist y nunca hiciste el proceso. Es mucho más rápido si automatizás con una auditoría como VibeAuditt para identificar qué falta, y luego corregís solo lo necesario. El monitoring semanal te alerta cuando un cambio rompe algo.
¿Lovable y Bolt me protegen de XSS automáticamente?
Parcialmente. Frameworks modernos como React escapan strings por defecto, eliminando XSS reflejado en la mayoría de casos. Pero si usás `dangerouslySetInnerHTML` o `innerHTML` directamente (que estos builders sugieren a veces para flexibilidad), abrís el vector. Y sin CSP, un XSS exitoso puede hacer lo que quiera.
¿Qué hago si ya publiqué con vulnerabilidades?
Primero auditá. Si tenés claves expuestas, rotalas inmediatamente (dashboard de Stripe / OpenAI / Supabase). Si tu Supabase no tiene RLS, deshabilitá el acceso público hasta que apliques políticas. Si tenés .git/ expuesto, configurá tu hosting para 404 en ese path. Luego seguí el resto del checklist.
¿Tengo que configurar todos los headers de seguridad manualmente?
Sí, los builders no los configuran por default. En Cloudflare Pages podés usar `_headers`, en Vercel `vercel.json` con `headers:`, en Netlify `netlify.toml`. Es config simple pero requiere 15 minutos. Sin headers, perdés la primera línea de defensa contra XSS, clickjacking y MITM.
// 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.
10 auditorías gratis al mes · reportes detallados desde 9 USD