Mike (@mikerb95)CodeByMike
Volver
/securityAuditoría OWASP

Seguridad, con evidencia.

Una revisión real sobre el código de este sitio, mapeada a OWASP Top 10. Cada hallazgo enlaza al commit exacto que lo corrigió - verificable en el historial público del repo, no una promesa.

Filtro en vivo· reparto real de 30 días
  1. 01Plataformafuera de este sitio
  2. 02Clasificador824 detectados
  3. 03Señuelos156 atrapados
  4. 04Cron horario0 bloqueos

Reparto entre categorías: real, últimos 30 días. La cantidad de tráfico limpio es ilustrativa: el sitio no cuenta las visitas limpias.

/01Security Operations · en vivo

Lo que llegó este mes, y qué pasó con ello.

Este sitio corre su propio motor de observabilidad de seguridad (un "micro-SIEM"): un clasificador de amenazas alineado con OWASP corre en el middleware de cada request, endpoints señuelo confirman intención maliciosa, y un cron horario aplica bloqueos temporales y detecta anomalías por estadística (z-score sobre una baseline de 30 días). Los números de abajo son agregados reales de los últimos 30 días.

Calculado este minuto · se renueva cada 3 h

Detectados (30d)
824
Bloqueos automáticos
0
Categorías detectadas
6

Desglose por categoría

  • Reincidencia bloqueada429
  • Reconocimiento de CMS/paneles194
  • Endpoint señuelo tocado156
  • Búsqueda de secretos/config41
  • csp_violation3
  • Path traversal / LFI1

Origen geográfico

  • US163
  • DE87
  • HK75
  • SE74
  • BO65
  • SG63

Tendencia diaria (14 días)

2026-10-08: 20 eventos
2026-10-09: 22 eventos
2026-10-10: 17 eventos
2026-10-11: 17 eventos
2026-10-12: 21 eventos
2026-10-13: 25 eventos
2026-10-14: 19 eventos
2026-10-15: 19 eventos
2026-10-16: 16 eventos
2026-10-17: 17 eventos
2026-10-18: 16 eventos
2026-10-19: 20 eventos
2026-10-20: 18 eventos
2026-10-21: 1 eventos
2026-10-082026-10-21

Cómo funciona (4 capas)

  1. 01Mitigación DDoS automática y firewall de la plataforma (Vercel).
  2. 02Clasificador propio en el middleware: firmas OWASP, rate limiting durable de dos capas, bloqueo por IP.
  3. 03Registro de cada evento hostil (con deduplicación de ráfagas) y endpoints señuelo que confirman intención maliciosa.
  4. 04Cron horario: agrega, detecta anomalías estadísticas, auto-bloquea reincidentes y alerta.

Objetivos de SLO del pipeline

  • Overhead añadido por request (p99)≤ 5 ms
  • Detección → bloqueo automático≤ 60 min
  • Detección → alerta (crítico)≤ 60 s
  • Falsos positivos de bloqueo< 0.1%
  • Retención de eventos crudos90 días

Por diseño, esta página nunca muestra IPs completas, nombres exactos de reglas de detección ni cuáles rutas son endpoints señuelo - eso le daría el manual de juego a un atacante.

/02Hallazgos corregidos · 13

Cada hallazgo, cerrado por un commit.

Al bajar, cada commit cierra su hallazgo.

  1. Auditoría del panel · septiembre 2026 · 9

  2. Dos endpoints del panel respondían sin sesión

    altacorregido
    A01:2021 · Broken Access Controlsrc/pages/api/admin/github/toggle.ts
    Problema

    La página de repositorios del panel usaba dos endpoints que vivían en /api/github/, fuera de la ruta que protege el middleware. Sin iniciar sesión, uno listaba los repositorios privados a los que llega el token del servidor y el otro creaba proyectos o cambiaba su visibilidad.

    Corrección

    Los dos se movieron bajo /api/admin/github/, donde el middleware exige la sesión de administrador. Desde entonces, toda ruta que usa el panel vive bajo /api/admin.

    2026-09-22 · commit 5ccb7ec ↗
  3. Una barra al final de la URL saltaba el login

    mediacorregido
    A01:2021 · Broken Access Controlsrc/middleware.ts
    Problema

    Los guardas del middleware comparan rutas literales. Con una barra final, /docs/presentacion/ no coincidía con la ruta privada y la presentación privada se servía sin iniciar sesión.

    Corrección

    El middleware calcula la ruta canónica una sola vez (sin barra final y sin prefijo de idioma) y todos los guardas comparan contra ella. Un test fija que la ruta llega igual con barra, sin ella y bajo /en.

    2026-09-22 · commit f978724 ↗
  4. El reto de las llaves de acceso lo controlaba el navegador

    mediacorregido
    A07:2021 · Identification and Authentication Failuressrc/lib/webauthn.ts
    Problema

    El reto (challenge) de WebAuthn viajaba en una cookie sin firmar. Quien tuviera una respuesta de llave capturada podía volver a presentarla escribiendo en la cookie el reto viejo: el servidor no recordaba qué reto había emitido.

    Corrección

    El reto se guarda en el servidor, en una tabla propia y con vencimiento, y se consume una sola vez. Un reto inventado o repetido ya no existe en la base y la verificación falla.

    2026-09-23 · commit f723b6b ↗
  5. Registrar una llave de acceso no pedía volver a entrar

    mediacorregido
    A07:2021 · Identification and Authentication Failuressrc/pages/api/admin/webauthn/registration/verify.ts
    Problema

    Con una cookie de sesión robada bastaba para registrar una llave de acceso propia en la cuenta, y esa llave seguía abriendo el panel aunque después se revocara la sesión.

    Corrección

    Dar de alta una llave exige haber iniciado sesión hace menos de 10 minutos. Cada alta o baja envía un aviso inmediato y queda registrada en el micro-SIEM.

    2026-09-23 · commit ddc84af ↗
  6. La allowlist confiaba en el nombre de usuario de GitHub

    mediacorregido
    A07:2021 · Identification and Authentication Failuresauth.config.ts
    Problema

    El acceso al panel se decidía por el nombre de usuario de GitHub, que se puede cambiar y deja libre el nombre anterior. Quien registrara ese nombre liberado pasaría la allowlist.

    Corrección

    El login exige además el id numérico de la cuenta, que GitHub no reasigna nunca.

    2026-09-23 · commit ddc84af ↗
  7. La actividad pública mostraba repositorios privados

    altacorregido
    A01:2021 · Broken Access Controlsrc/pages/api/github/activity.ts
    Problema

    El endpoint que alimenta /log y la portada usa un token que también ve los repositorios privados, y publicaba sus nombres y mensajes de commit, incluidos los de proyectos de clientes.

    Corrección

    Lo privado cuenta en las cifras pero nunca sale por nombre ni por mensaje: solo se publica un repositorio marcado explícitamente como público. Una URL con parámetros redirige a la limpia, para que nadie se salte la caché.

    2026-09-23 · commit 1a4ecf7 ↗
  8. La vitrina de seguridad publicaba el uso del panel como ataques

    mediacorregido
    A04:2021 · Insecure Designsrc/lib/security/audit.ts
    Problema

    La tabla de eventos guarda también el rastro de acciones legítimas (entradas al panel, clientes consultando sus pagos). Esta misma página las contaba como amenazas, su gráfico diario delataba qué días se usaba el panel, y el bloqueo masivo podía bloquear a clientes y alumnos.

    Corrección

    Las categorías de rastro se separaron de las de amenaza en un solo lugar del código y quedan fuera de esta página, de las anomalías y del bloqueo masivo. El rastro se consulta aparte, solo en el panel.

    2026-09-24 · commit 5bfcfdf ↗
  9. Una sesión revocada volvía a entrar con la base caída

    mediacorregido
    A07:2021 · Identification and Authentication Failuressrc/middleware.ts
    Problema

    El middleware comprobaba si una sesión del panel estaba revocada, pero si la base de datos no respondía la dejaba pasar. Una caída, o una cuota agotada a propósito, volvía válida cualquier cookie robada y ya revocada.

    Corrección

    Esa comprobación falla cerrada: si no se puede saber si la sesión fue revocada, el panel responde 503. Es la excepción deliberada a la regla de fallar abierto del resto de la seguridad del sitio.

    2026-09-24 · commit 5bfcfdf ↗
  10. JSON incrustado en <script> sin escapar

    mediacorregido
    A03:2021 · Injectionsrc/lib/json-script.ts
    Problema

    Veinticinco sitios incrustaban JSON en etiquetas <script>, entre ellos los datos estructurados públicos con títulos de proyecto leídos de la base. Un título con "</script>" cerraba la etiqueta y lo que siguiera se ejecutaba como código.

    Corrección

    Todos pasan por una sola función que escapa <, > y & antes de incrustar, y los enlaces que salen de la base se validan para aceptar solo http y https.

    2026-09-24 · commit f2a9496 ↗
  11. Primera auditoría · julio 2026 · 4

  12. XSS almacenado vía extensión de archivo no validada

    altacorregido
    A03:2021 · Injectionsrc/pages/api/admin/upload.ts
    Problema

    El endpoint de subida de certificados derivaba la extensión del archivo final del nombre enviado por el cliente, y solo validaba el MIME type declarado por el propio cliente (fácilmente falsificable). Un archivo subido como "foo.html" con Content-Type falso quedaba servido como HTML desde /assets/certs. SVG estaba además en la lista de tipos permitidos, y un SVG puede contener <script> ejecutable.

    Corrección

    La extensión ahora se deriva de un mapa fijo MIME → extensión, nunca del nombre del archivo. SVG se eliminó de los tipos permitidos.

    2026-07-06 · commit d439054 ↗
  13. Formulario de contacto sin límite de tasa ni validación

    mediacorregido
    A04:2021 · Insecure Designsrc/pages/api/contact.ts
    Problema

    El endpoint público de contacto aceptaba inserciones ilimitadas a la base de datos sin rate limit, sin validar formato de email ni longitud de campos, y un JSON malformado producía un error 500 sin manejar.

    Corrección

    Se aplicó el rate limiter ya existente en el proyecto (5 envíos/min por IP), validación de formato de email, límites de longitud por campo y manejo explícito de JSON inválido.

    2026-07-06 · commit 779f4b0 ↗
  14. Sesiones sin claim de login evadían la allowlist

    mediacorregido
    A01:2021 · Broken Access Controlsrc/middleware.ts
    Problema

    El middleware solo revalidaba la allowlist de GitHub cuando la sesión traía el claim "login". Una sesión sin ese claim pasaba el chequeo por defecto en vez de ser rechazada.

    Corrección

    El gate de admin ahora exige el claim en toda sesión: sin login válido en la allowlist, la respuesta es 403 sin excepción.

    2026-07-06 · commit 14b08e2 ↗
  15. Faltaban CSP y HSTS en las respuestas

    mediacorregido
    A05:2021 · Security Misconfigurationsrc/middleware.ts
    Problema

    El middleware ya fijaba varios headers de seguridad (nosniff, Referrer-Policy, X-Frame-Options en admin) pero no incluía Content-Security-Policy ni Strict-Transport-Security en ninguna ruta.

    Corrección

    Se agregó CSP restrictiva (default-src self, frame-ancestors none) y HSTS con preload a todas las respuestas, públicas y de admin.

    2026-07-06 · commit 32bcf0d ↗
/03Controles ya vigentes

Lo que ya estaba bien, demostrado.

Webhooks de pago verificados

El receptor de eventos de Wompi verifica la firma HMAC antes de procesar cualquier evento, rechaza eventos con más de 6 horas de antigüedad (anti-replay) y valida que el monto/moneda coincidan con el pago esperado antes de aplicar una transición de estado.

Secretos cifrados en reposo

La bóveda de credenciales cifra cada valor con AES-256-GCM (cifrado autenticado) antes de escribirlo en la base de datos. La clave vive solo en el entorno del servidor, nunca en la BD ni en los backups.

Defensa en profundidad en /admin

La autorización se revalida contra la allowlist tanto en el callback de login como en el middleware, en cada request - no basta con haber iniciado sesión una vez.

Consultas parametrizadas

Todo el acceso a datos pasa por Drizzle ORM con consultas parametrizadas; no hay interpolación de strings en SQL en ninguna ruta del proyecto.

/04Metodología

Cómo se hizo.

  1. Revisar

    Dos revisiones del código real de este repositorio, no un ejercicio teórico. La de julio cubrió control de acceso, subida de archivos, endpoints públicos, secretos, webhooks de pago y headers de respuesta; la de septiembre, el panel: llaves de acceso, sesiones y su revocación, el rastro de auditoría y lo que el sitio publica a partir de la base.

  2. Corregir

    Cada hallazgo se clasificó contra OWASP Top 10 (2021) y enlaza el commit que lo cerró. Algunos commits cierran dos hallazgos, y alguno trae otros cambios junto al arreglo: el enlace lleva al commit real, no a uno preparado para esta página. Cada corrección pasó por la suite de tests y un build completo.

  3. Publicar

    Esta página se actualiza con cada auditoría. Solo se publica lo corregido: lo que una revisión deja pendiente no aparece aquí hasta que se cierra. Lo que ves es el estado real del código en producción.