FOLIO: AUD-20260925-004 Secuencia: Auditoría #004 del día 2026-09-25
📅 2026-09-25 — COT 👤 Auditor: Anderson Mares (AMR TECH) ● VIGENTE
Auditoría de Seguridad · Análisis Comparativo de CRM

Seguridad CRM: trycompai/crm vs WhatsApp CRM

Evaluación de vulnerabilidades del CRM externo open-source y del CRM PHP propio. Recomendación de adopción y plan de acción para reforzar la seguridad actual.

3 CRÍTICAS encontradas 4 MEDIAS 5 CONTROLES OK Recomendación: NO migrar

1. Resumen Ejecutivo

trycompai/crm (externo)

CRM open-source orientado a agentes IA. Stack moderno: TypeScript · Bun · Next.js · NestJS · Prisma · Postgres. Diseñado para equipos de ventas B2B con integración de email (Gmail/Outlook). No es compatible con tu caso de uso (WhatsApp/Meta webhooks, Hostinger, PHP).

Adopción: NO RECOMENDADA
WhatsApp CRM (propio — PHP)

CRM PHP custom con soporte WhatsApp, Facebook Messenger, Instagram DM. Funcional y adaptado al negocio. Tiene 3 vulnerabilidades críticas que deben corregirse antes de continuar en producción.

Estado: REQUIERE CORRECCIONES URGENTES

2. Análisis: trycompai/crm (repo externo)

Stack tecnológico

Frontend
Next.js App Router · shadcn/ui
API
NestJS + nestjs-trpc · :3001
Agent
eve (Vercel) · 18 tools · 4 skills
Data
Prisma · Postgres (Neon) · Redis opt.

Puntos de seguridad positivos

Better Auth — librería de auth moderna, bien mantenida. Google/Microsoft OAuth. Falla cerrado si ALLOWED_SIGN_IN no está configurado.
Sandbox con deny-all egress — el agente no puede hacer llamadas directas a internet desde el sandbox, solo vía funciones controladas.
CRON_SECRET — la ruta de sync de mailboxes no puede ejecutarse sin un secreto configurado.
HTTPS obligatorio en producción — cookies seguras activadas con NODE_ENV=production.
TypeScript end-to-end — type-safe desde la fila Prisma hasta la tabla UI, reduce bugs de tipo.
Proceso de reporte privado — vulnerabilidades reportadas en privado vía SECURITY.md, no en issues públicos.

Riesgos y limitaciones para tu caso de uso

MEDIUM
Sin RBAC — autorización binaria

Todo usuario autenticado puede leer y escribir todos los registros. No hay roles, permisos por registro ni organizaciones. Si hay más de un operador, todos ven todo.

MEDIUM
El agente lee todos los correos (GDPR)

Gmail/Calendar access es condición del sign-in. El agente lee cuerpos de mensajes y asistentes de reuniones de personas que no se registraron en el CRM. Tú eres el data controller.

MEDIUM
Datos enviados a terceros con API keys opcionales

Perplexity, LinkedIn y otros vendors reciben nombres, dominios de email y empleadores si se configuran sus API keys. Sin keys, solo salen llamadas a Google APIs.

INFO
Lock-in en Vercel + Neon + Bun

El agente usa eve (framework Vercel propio). El runtime de durable sessions depende de la infraestructura de Vercel. Difícil de migrar a Hostinger.

VEREDICTO: No recomendado para reemplazar tu CRM
  • Está diseñado para email (Gmail/Outlook), no para WhatsApp/Meta webhooks
  • Requiere OAuth Google/Microsoft — no tiene login por contraseña ni API key propia
  • Deploy en Vercel obligatorio — incompatible con Hostinger shared hosting
  • Stack completamente diferente (TypeScript/Bun vs PHP/Docker)
  • Migrar implicaría reescribir desde cero todas tus integraciones (WAHA, Meta Webhook, AIService, GoPlay)

Sí se pueden adoptar sus patrones de diseño (work queue, evidence ledger, agent skills) adaptados a PHP.

3. Vulnerabilidades: WhatsApp CRM (PHP propio)

Críticas — Corregir antes de usar en producción

CRÍTICA #1 Contraseña hardcodeada: "admin"
auth.php:17

La contraseña del CRM está literalmente en el código fuente como $crm_password = "admin". Cualquiera que acceda al repositorio (o que pruebe contraseñas triviales) tiene acceso completo al CRM.

// auth.php línea 17
$crm_password = "admin"; ← ELIMINAR
// FIX: leer desde .env
$crm_password = getenv('CRM_PASSWORD') ?: die('CRM_PASSWORD no configurada');

En .env: CRM_PASSWORD=tu_contraseña_segura_aqui

CRÍTICA #2 install.php accesible sin autenticación
public/install.php

El archivo install.php ejecuta init.sql sin ninguna verificación de autenticación. Cualquier visitante puede hacer una petición GET y reinicializar (destruir) la base de datos. Este archivo no debe existir en producción.

// install.php — cero protección
require_once __DIR__ . '/../src/Database.php';
$pdo->exec($sql); ← reinicializa toda la DB
FIX inmediato: eliminar o renombrar a install.php.disabled en producción. O agregar require_login(); al inicio del archivo y un confirm antes de ejecutar.
CRÍTICA #3 Sin rate-limiting en login — fuerza bruta desprotegida
login.php

El formulario de login no tiene ningún mecanismo anti-bruta-fuerza: ni delay, ni captcha, ni bloqueo por intentos. Un atacante puede probar miles de contraseñas automáticamente sin limitación.

FIX: agregar contador de intentos en sesión + delay creciente. Con >5 intentos fallidos en 10 min → bloquear IP por 30 min (con APCu o archivo de lock).

Medias — Corregir en próximo sprint

MEDIA #4
Session fixation — no se regenera el ID de sesión al hacer login

login.php establece $_SESSION['logged_in'] = true sin llamar primero a session_regenerate_id(true). Un atacante que inyecte un session ID previo mantiene acceso.

// En login.php, justo antes de redirigir:
session_regenerate_id(true);
MEDIA #5
Sin protección CSRF en el formulario de login

El formulario POST de login.php no valida ningún token CSRF. Un atacante puede crear un formulario externo que haga login con credenciales conocidas.

// Generar en auth.php: $_SESSION['csrf'] = bin2hex(random_bytes(16));
// Validar en login.php: hash_equals($_SESSION['csrf'], $_POST['csrf'])
MEDIA #6
Parser .env expone mensajes de error internos

En varios endpoints (send_message.php, webhook.php) el catch genérico imprime $e->getMessage() directamente en la respuesta JSON. Puede revelar rutas de servidor, configuración interna o credenciales.

// FIX: solo loguear internamente, responder genérico
error_log($e->getMessage());
echo json_encode(['status' => 'error', 'msg' => 'Error interno']);
MEDIA #7
Parser .env sin manejo de comillas ni valores con espacios

El parser manual del .env no soporta valores entre comillas (KEY="valor con espacios") ni escapes. Valores complejos (contraseñas con =, URLs) se truncan silenciosamente.

// FIX: usar vlucas/phpdotenv (Composer) o al menos trim de comillas:
$value = trim($parts[1], "\"' \t\n\r");

Controles correctamente implementados

webhook.php usa hash_equals()

Comparación en tiempo constante para el token, protege contra timing attacks.

PDO con prepared statements

send_message.php y otros usan $pdo->prepare() — protegido contra SQL injection.

meta-webhook.php valida firma HMAC

Valida X-Hub-Signature-256 de Meta antes de procesar cualquier payload.

require_login() en endpoints críticos

send_message.php y otros endpoints sensibles verifican sesión activa.

htmlspecialchars() en outputs HTML

login.php escapa el mensaje de error con htmlspecialchars — protegido contra XSS reflejado.

4. Recomendación Final y Plan de Acción

trycompai/crm es seguro y bien diseñado como producto, pero es incompatible con tu infraestructura (Hostinger + PHP + WhatsApp/Meta). No es una mejora — es una plataforma completamente diferente. Mantén tu CRM PHP y corrije las 3 vulnerabilidades críticas.

Plan de correcciones por prioridad

1
HOY: Mover contraseña de auth.php a .env como CRM_PASSWORD. Desactivar install.php en producción (chmod 000 o mover fuera de public/).
2
ESTA SEMANA: Agregar session_regenerate_id(true) en login. Agregar token CSRF al formulario. Agregar rate limiting (5 intentos / 10 min) en login.php.
3
PRÓXIMO SPRINT: Sanitizar messages de error en respuestas JSON. Mejorar parser .env con soporte de comillas. Considerar vlucas/phpdotenv via Composer.

Patrones de trycompai que sí se pueden adoptar (adaptados a PHP)

Work Queue + leasing

Su claimDue con FOR UPDATE SKIP LOCKED evita duplicados cuando hay múltiples workers. Aplica a tu cola WhatsApp → GoPlay.

Evidence Ledger

Nunca guardar datos deducidos como hechos. Solo lo observado explícitamente. Patrones sólidos para AIService.

Agent Skills como archivos .md

Skills versionadas con el código. Tu AIService puede leer un system_prompt.md versionado en lugar de tenerlo hardcodeado en PHP.

5. Checklist de Seguridad Resumido

Control trycompai/crm WhatsApp CRM (PHP)
Auth segura (no contraseña simple) ✓ OAuth Google/Microsoft ✗ "admin" hardcodeado
Protección SQL Injection ✓ Prisma ORM ✓ PDO + prepared statements
Protección XSS ✓ React (escaping automático) ⚠ Parcial (htmlspecialchars en login, no en otros)
Protección CSRF ✓ Better Auth ✗ Sin token CSRF
Rate limiting login ✓ Better Auth built-in ✗ Sin protección
Webhooks con firma HMAC N/A (sin webhooks) ✓ hash_equals + Meta X-Hub-Signature
Archivos de instalación protegidos ✓ No hay install scripts públicos ✗ install.php accesible sin auth
Session fixation protegida ✓ Better Auth ✗ Sin session_regenerate_id()
HTTPS en producción ✓ Obligatorio ✓ Hostinger SSL activo
Secretos en variables de entorno ✓ .env.example sin valores reales ✗ Contraseña en código fuente
AMR TECH™ · Desarrollado / Auditado por Anderson Mares
AUD-20260925-004 ● VIGENTE