Files
Horux360/docs/security/2026-03-18-security-audit-remediation.md
Consultoria AS 351b14a78c security: comprehensive security audit and remediation (20 fixes)
CRITICAL fixes:
- Restrict X-View-Tenant impersonation to global admin only (was any admin)
- Add authorization to subscription endpoints (was open to any user)
- Make webhook signature verification mandatory (was skippable)
- Remove databaseName from JWT payload (resolve server-side with cache)
- Reduce body size limit from 1GB to 10MB (50MB for bulk CFDI)
- Restrict .env file permissions to 600

HIGH fixes:
- Add authorization to SAT cron endpoints (global admin only)
- Add Content-Security-Policy and Permissions-Policy headers
- Centralize isGlobalAdmin() utility with caching
- Add rate limiting on auth endpoints (express-rate-limit)
- Require authentication on logout endpoint

MEDIUM fixes:
- Replace Math.random() with crypto.randomBytes for temp passwords
- Remove console.log of temporary passwords in production
- Remove DB credentials from admin notification email
- Add escapeHtml() to email templates (prevent HTML injection)
- Add file size validation on FIEL upload (50KB max)
- Require TLS for SMTP connections
- Normalize email to lowercase before uniqueness check
- Remove hardcoded default for FIEL_ENCRYPTION_KEY

Also includes:
- Complete production deployment documentation
- API reference documentation
- Security audit report with remediation details
- Updated README with v0.5.0 changelog
- New client admin email template
- Utility scripts (create-carlos, test-emails)
- PM2 ecosystem config updates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 22:32:04 +00:00

8.3 KiB

Auditoría de Seguridad y Remediación - Horux360

Fecha: 2026-03-18 Auditor: Claude Opus 4.6 Alcance: Plataforma completa (API, Frontend, Infraestructura)


Resumen Ejecutivo

Se realizó una auditoría de seguridad completa de la plataforma Horux360 antes de abrirla a clientes. Se identificaron 6 vulnerabilidades críticas, 9 altas, 10 medias y 7 bajas. Se corrigieron 20 vulnerabilidades (todas las críticas, altas y medias de código).

Vulnerabilidades Corregidas

CRÍTICAS (6)

C1. Impersonación de Tenant sin Restricción

  • Archivo: tenant.middleware.ts, plan-limits.middleware.ts
  • Problema: Cualquier usuario con role === 'admin' (incluidos los admins de clientes) podía usar el header X-View-Tenant para acceder a los datos de CUALQUIER otro tenant.
  • Fix: Se creó utils/global-admin.ts con función isGlobalAdmin() que verifica que el tenant del usuario solicitante tenga el RFC del admin global (CAS2408138W2). Se aplicó en tenant.middleware.ts y plan-limits.middleware.ts.
  • Impacto: Rompía completamente el aislamiento multi-tenant.

C2. Endpoints de Suscripción sin Autorización (IDOR)

  • Archivo: subscription.routes.ts, subscription.controller.ts
  • Problema: Cualquier usuario autenticado podía llamar POST /api/subscriptions/:tenantId/mark-paid para marcar cualquier tenant como pagado.
  • Fix: Se agregó authorize('admin') en las rutas y verificación isGlobalAdmin() en cada método del controlador. Doble capa de protección.
  • Impacto: Bypass total de pagos.

C3. Bypass de Verificación de Webhook de MercadoPago

  • Archivo: webhook.controller.ts, mercadopago.service.ts
  • Problema: (1) Si faltaba el header x-signature, la verificación se saltaba completamente. (2) Si MP_WEBHOOK_SECRET no estaba configurado, la función retornaba true siempre.
  • Fix: Ahora es obligatorio que los headers x-signature, x-request-id y data.id estén presentes; de lo contrario se rechaza con 401. Si MP_WEBHOOK_SECRET no está configurado, se rechaza el webhook.
  • Impacto: Un atacante podía forjar webhooks para activar suscripciones gratis.

C4. databaseName Expuesto en JWT

  • Archivo: auth.service.ts, packages/shared/src/types/auth.ts, tenant.middleware.ts
  • Problema: El nombre interno de la base de datos PostgreSQL se incluía en el JWT (base64, visible para cualquier usuario).
  • Fix: Se eliminó databaseName del payload JWT y del tipo JWTPayload. El tenant middleware ahora resuelve el databaseName server-side usando tenantId con caché de 5 minutos.
  • Impacto: Fuga de información de infraestructura interna.

C5. Body Size Limit de 1GB

  • Archivo: app.ts, cfdi.routes.ts, deploy/nginx/horux360.conf
  • Problema: Express y Nginx aceptaban payloads de hasta 1GB, permitiendo DoS por agotamiento de memoria.
  • Fix: Límite global reducido a 10mb. Ruta /api/cfdi/bulk tiene límite específico de 50mb. Nginx actualizado a 50M.
  • Impacto: Un solo request malicioso podía crashear el servidor.

C6. Archivo .env con Permisos 644

  • Archivo: apps/api/.env
  • Problema: El archivo .env era legible por cualquier usuario del sistema.
  • Fix: chmod 600 — solo legible por el propietario (root).

ALTAS (5)

H1. SAT Cron Endpoints sin Autorización

  • Archivo: sat.routes.ts, sat.controller.ts
  • Problema: Cualquier usuario autenticado podía ejecutar el cron global de sincronización SAT.
  • Fix: Se agregó authorize('admin') en rutas y isGlobalAdmin() en el controlador.

H2. Sin Content Security Policy (CSP)

  • Archivo: deploy/nginx/horux360.conf
  • Problema: Sin CSP, no había protección del navegador contra XSS.
  • Fix: Se agregó CSP header completo. Se removió X-XSS-Protection (deprecado). Se agregó Permissions-Policy.

H3. Tenant CRUD con Admin Genérico

  • Archivo: usuarios.controller.ts
  • Problema: El check isGlobalAdmin() estaba duplicado y no centralizado.
  • Fix: Se centralizó en utils/global-admin.ts con caché para evitar queries repetidos.

H4. Sin Rate Limiting en Auth

  • Archivo: auth.routes.ts
  • Problema: Sin límite de intentos en login/register/refresh.
  • Fix: express-rate-limit instalado con: login 10/15min, register 3/hora, refresh 20/15min por IP.

H5. Logout Público

  • Archivo: auth.routes.ts
  • Problema: El endpoint /auth/logout no requería autenticación.
  • Fix: Se agregó authenticate middleware.

MEDIAS (9)

# Problema Fix
M1 Contraseñas temporales con Math.random() Cambiado a crypto.randomBytes(4).toString('hex')
M2 Contraseñas temporales logueadas a console Removido console.log
M3 Credenciales de BD enviadas por email Removida sección de conexión DB del template de email
M4 HTML injection en templates de email Agregado escapeHtml() en todos los valores interpolados
M5 Sin validación de tamaño en upload de FIEL Límite de 50KB por archivo, 256 chars para password
M6 SMTP sin requerir TLS Agregado requireTLS: true en config de Nodemailer
M7 Email no normalizado en registro toLowerCase() aplicado antes del check de duplicados
M8 FIEL_ENCRYPTION_KEY con default hardcoded Removido .default(), ahora es requerido
M9 Plan limits bypass con X-View-Tenant Mismo fix que C1, verificación isGlobalAdmin()

Vulnerabilidades Pendientes (Infraestructura)

Estas requieren cambios de infraestructura que no son código:

# Severidad Problema Recomendación
P1 ALTA App corre como root Crear usuario horux dedicado
P2 MEDIA PostgreSQL usa superuser Crear usuario horux_app con permisos mínimos
P3 MEDIA Backups sin encriptar ni offsite Agregar GPG + sync a S3
P4 MEDIA Sin lockout de cuenta Agregar contador de intentos fallidos (requiere migración DB)
P5 BAJA Tokens JWT en localStorage Migrar a HttpOnly cookies (requiere cambios frontend + API)
P6 BAJA Mismo JWT secret para access y refresh Agregar JWT_REFRESH_SECRET

Archivos Modificados

Nuevos

  • apps/api/src/utils/global-admin.ts — Utilidad centralizada para verificar admin global con caché

Modificados (Seguridad)

  • apps/api/src/middlewares/tenant.middleware.ts — Resolución de databaseName server-side + global admin check
  • apps/api/src/middlewares/plan-limits.middleware.ts — Global admin check para bypass
  • apps/api/src/controllers/subscription.controller.ts — Global admin authorization
  • apps/api/src/controllers/webhook.controller.ts — Verificación de firma obligatoria
  • apps/api/src/controllers/sat.controller.ts — Global admin check en cron endpoints
  • apps/api/src/controllers/usuarios.controller.ts — Uso de utilidad centralizada
  • apps/api/src/controllers/fiel.controller.ts — Validación de tamaño de archivos
  • apps/api/src/routes/auth.routes.ts — Rate limiting + logout autenticado
  • apps/api/src/routes/subscription.routes.ts — authorize('admin') middleware
  • apps/api/src/routes/sat.routes.ts — authorize('admin') en cron endpoints
  • apps/api/src/routes/cfdi.routes.ts — Límite de 50MB específico para bulk
  • apps/api/src/services/auth.service.ts — databaseName removido de JWT, email normalizado
  • apps/api/src/services/usuarios.service.ts — randomBytes + sin console.log
  • apps/api/src/services/email/email.service.ts — requireTLS
  • apps/api/src/services/email/templates/new-client-admin.ts — Sin DB credentials, con escapeHtml
  • apps/api/src/services/payment/mercadopago.service.ts — Rechazar si no hay secret
  • apps/api/src/config/env.ts — FIEL_ENCRYPTION_KEY requerido
  • apps/api/src/app.ts — Body limit 10MB
  • packages/shared/src/types/auth.ts — databaseName removido de JWTPayload
  • deploy/nginx/horux360.conf — CSP, Permissions-Policy, body 50M

Prácticas Positivas Encontradas

  • bcrypt con 12 salt rounds
  • HTTPS con HSTS, TLS 1.2/1.3
  • Helmet.js activo
  • SQL parameterizado en todas las queries raw (Prisma ORM)
  • FIEL encriptado con AES-256-GCM
  • Refresh token rotation implementada
  • Base de datos por tenant (aislamiento a nivel DB)
  • PostgreSQL solo escucha en localhost
  • .env en .gitignore y nunca commiteado