Resumen del día
Hoy no hubo features nuevas. Hubo algo más útil: revisamos todo el sitio con ojos de atacante. Partimos de un reporte de ciberseguridad externo, lo validamos contra el código real, y parcheamos lo que tenía riesgo concreto. Total: 8 archivos modificados, cero funcionalidad rota.
El problema con el vibe coding y la seguridad
Cuando construyes rápido con IA, el código funciona. Las features aparecen. Los tests pasan. Pero hay una categoría de problemas que el vibe coding sistemáticamente ignora: los que no rompen nada hoy pero te pueden quemar mañana.
✓ Funcionalidad visible
✓ UX y diseño
✓ SEO y conversión
✗ Superficie de ataque
✗ Validación de inputs
✗ Aislamiento de terceros
✗ Sanitización de HTML
✓ dangerouslySetInnerHTML sin sanitizar
✓ Validación inconsistente en APIs
✓ Params de URL sin re-validar
✓ Headers de seguridad
✓ Exposición de keys
✓ Dependencias vulnerables
✓ Auth en rutas protegidas
La trampa es que nada de esto falla en desarrollo. Un iframe sin sandbox muestra los testimonios igual. Un dangerouslySetInnerHTML sin sanitizar renderiza el markdown igual. El form sin Zod acepta datos igual. Solo falla cuando alguien malo llega con inputs maliciosos.
Qué hicimos hoy
Paso 1: Analizar el reporte vs. el código real
El reporte externo tenía 5 hallazgos. Al validarlos contra el código, 3 eran falsos positivos:
| Hallazgo del reporte | ¿Existe? | Por qué no |
|---|---|---|
| Google Maps API key expuesta | ❌ No | Esa key no existe en el proyecto |
| Sin CSP configurada | ❌ No | Hay CSP completa en next.config.ts |
| Next.js desactualizado | ❌ No | 15.5.12, versión actual |
Lección: Los reportes automáticos generan ruido. Siempre validar contra el código antes de actuar.
Los 2 hallazgos reales:
- iframes de SocialJuice sin
sandbox— 5 instancias en el sitio dangerouslySetInnerHTMLen SlideRenderer sin sanitizar — datos de presentaciones inyectados directo al DOM
Paso 2: Priorizar sin paranoia
No todo lo que suena a vulnerabilidad vale el esfuerzo. El criterio que usé:
Paso 3: Los fixes
iframes de SocialJuice (5 archivos) — agregué sandbox y referrerPolicy:
<!-- Antes -->
<iframe src="https://embed.socialjuice.io/..." allowFullScreen />
<!-- Después -->
<iframe
src="https://embed.socialjuice.io/..."
allowFullScreen
sandbox="allow-scripts allow-popups allow-forms allow-same-origin"
referrerPolicy="no-referrer"
/>
Si SocialJuice es comprometido, el sandbox limita lo que puede hacer dentro del iframe. Sin él, un script malicioso en embed.socialjuice.io corre con acceso completo al DOM de gabrielneuman.com.
SlideRenderer — renderBody() y renderHeading() ahora pasan por sanitizeMarkdownHtml() antes del dangerouslySetInnerHTML. La función ya existía en el proyecto, nadie la había conectado acá.
/api/p/[id]/lead — reemplazado el .trim() manual por schema Zod completo con tipos, longitudes máximas y validación de email. Más limpio y más seguro.
/api/cms/crm/contacts/[email] — una línea de Zod después del decodeURIComponent para rechazar cualquier string que no sea email válido antes de que llegue a MongoDB.
El proceso que funcionó
Aprendizaje clave
El vibe coding no es inseguro por naturaleza — es inseguro por omisión. Cuando le pides a la IA "hazme un form que guarde leads en MongoDB", lo hace. Pero no te va a preguntar "¿quieres validación Zod o solo .trim()?" a menos que se lo pidas.
La deuda de seguridad del vibe coding se acumula en silencio. Un audit cada 3-4 meses cubre esa brecha. No necesitas ser experto en OWASP — necesitas un checklist y tiempo para leer el código con ojos críticos.
Lo que más me llamó la atención hoy: sanitizeMarkdownHtml ya existía en el proyecto. Alguien (yo, hace meses) la escribió conscientemente. Pero SlideRenderer nunca la usó. El código seguro existía — nadie lo conectó.
La herramienta de seguridad más subutilizada siempre es la que ya tienes.
Pendientes
- Migrar rate limiter a Upstash Redis cuando el tráfico justifique (hoy no urgente)
- Hacer audit similar en Naira landing — mismo patrón de iframes y forms
- Commit y deploy de los 8 archivos modificados hoy