Dos avisos críticos la misma semana, 124 boletines cruzados contra las versiones reales y tres despliegues que se negaron a correr. Lo que protege un producto no es el modelo de IA que lleva dentro: es la fontanería.
Alexis Dorado Muñoz
Fundador de 24Bytes
27 de octubre de 2026
13 min de lectura

En los últimos siete días, los registros del servidor donde corren mis productos acumulan 13.097 intentos fallidos de inicio de sesión. Robots probando root, admin, ubuntu, uno detrás de otro, día y noche. Ninguno entró, y no por suerte.
Esa cifra es la parte aburrida. La semana interesante empezó el 20 de octubre, cuando coincidieron dos avisos de máxima gravedad: PHP2Shell, la ejecución remota de código en el núcleo de PHP que obligó a apagar portales de gobierno, y el boletín de seguridad de Node.js del 21 de octubre, con una corrupción de memoria en Brotli explotable en remoto. El primero no me tocaba —no tengo PHP en producción, y lo comprobé antes de decirlo—. El segundo sí: todas mis APIs son Node.
Lo que sigue es lo que hice en esos diez días, por qué cada decisión está donde está, y las tres veces que algo se negó a funcionar y resultó ser lo mejor que pasó en la semana.
Siete productos en producción sobre un mismo servidor —un monitor de contratación pública con clientes de pago y pasarela, una plataforma con inversores y dinero real, el sistema de un cliente con tres entornos, una fábrica de contenido, este sitio con su analítica y dos demos comerciales— reparten 63 contenedores entre producción, pruebas y demos. Si uno cae, caen la facturación y la reputación a la vez.
Por eso el principio número uno no es una herramienta, es una regla: ningún contenedor publica puertos a internet. Nunca. Todo el tráfico sigue la misma cadena, sin excepciones:
internet → Cloudflare (DNS + WAF) → túnel cifrado → proxy que escucha solo en la interfaz local → contenedor, por nombre
Las consecuencias prácticas de esa cadena valen más que cualquier antivirus:
Antes de parchear nada hay una pregunta más incómoda: ¿cómo sé que no han entrado ya? Un servidor comprometido al que le aplicas actualizaciones sigue comprometido.
La revisión fue de solo lectura y buscó lo que deja un intruso, no lo que deja un escáner: llaves autorizadas en todas las cuentas, sesiones aceptadas y fallidas, usuarios con shell, tareas programadas y servicios de arranque, procesos por consumo de CPU, binarios con permisos especiales, archivos de sistema modificados en los últimos días, conexiones salientes, imágenes y contenedores, y quién tiene montado el socket de Docker —que es, en la práctica, ser root—.
Resultado: una única llave en todas las cuentas, cero mineros, cero tareas desconocidas, cada conexión saliente explicada, cortafuegos denegando por defecto con una sola excepción. Cero intrusiones. Queda escrito como línea base para repetirlo cada mes: la próxima vez la pregunta no será «¿hay algo raro?», sino «¿qué cambió respecto a octubre?».
El perímetro es lo fácil. La revisión que importa es la del código de cada producto buscando las clases de fallo que mueven dinero o datos: autorización por ruta, acceso a datos de otro usuario, validación de firmas en los webhooks de pago, inyección, peticiones del servidor a URL que controla un tercero, subida de archivos.
Lo que estaba bien, y lo confirmé antes de dormir tranquilo: los webhooks de la pasarela validan la firma en tiempo constante, son idempotentes por triplicado contra el reenvío, re-consultan al proveedor y comparan el monto; el panel de inversores acota cada consulta al usuario autenticado y las operaciones con dinero exigen rol de administrador.
Lo que endurecí, y por qué cada capa suma:
.env*, de modo que un fichero de entorno podía viajar dentro de una imagen que nunca sale de mi servidor. Corregido en todos los repositorios, y las claves implicadas entran en el ciclo de rotación, que es trabajo manual a propósito.npm audit aplicado en seis repositorios; en uno, los críticos pasaron de cinco a cero.El problema de la seguridad operativa no es encontrar avisos: hay cientos cada semana. Es saber cuáles te alcanzan con tus versiones exactas y tu exposición real. El método que uso tiene tres pasos y el orden importa:
Ese tercer paso movió gravedades en las dos direcciones, que es la señal de que funciona. Un «crítico» resultó aplicar solo con una configuración que no uso. Y al revés: un aviso que parecía menor subió de golpe al mirar mi uso concreto —la librería que interpreta el cuerpo de las peticiones sí acepta contenido comprimido con Brotli, y corre antes de la autenticación. Eso convertía el fallo de Node en una ruta explotable sin credenciales, en todas las APIs a la vez.
Y de ahí salió la mejor relación coste/beneficio de la semana. Reconstruir todas las imágenes lleva horas; cerrar la puerta en el borde llevó dos minutos: el proxy rechaza con un 415 cualquier petición que llegue con el cuerpo comprimido. Ni los navegadores ni los webhooks legítimos comprimen el cuerpo de la petición, así que nadie lo nota. Probado desde fuera: br, gzip y deflate → 415; tráfico normal → 200. Una regla de una línea cerró una ruta de ejecución remota en siete productos mientras se reconstruía lo de verdad.
Después vino lo lento y aburrido, que es donde se gana: todas las APIs sobre Node 22 con las librerías corregidas, los dos servicios que seguían en Node 20 migrados —fin de soporte significa que el siguiente fallo no se arregla—, el framework del panel de cliente actualizado por tres denegaciones de servicio y un XSS en el renderizado de servidor, el almacenamiento de archivos de un cliente actualizado por un fallo que permitía subir y sobrescribir objetos sin autenticación, las bases a la versión con parche con respaldo previo y pasando por el entorno de pruebas antes que por producción, el proxy, el panel del proxy por un fallo de ejecución remota, y el cliente del túnel al día.
Esa última tiene coda: dos días después, el proveedor del túnel envió a todos sus operadores el aviso oficial pidiendo exactamente esa actualización. Ya estaba hecha y verificada desde fuera. El método no sirve para ir rápido: sirve para no depender de que alguien te avise.
Todo salió a producción por pull requests y con scripts en modo ensayo: cada uno imprime qué ficheros cambiarían y qué contenedores se recrearían, y solo ejecuta cuando escribes la palabra aplicar. Esa costumbre pagó la semana entera.
Primera. El ensayo de la copia al servidor mostró que iba a borrar credenciales que no viven en el repositorio —cuentas de servicio, tokens, llaves que se crearon allí—. Un --delete bienintencionado. Se reescribió para copiar solo las rutas que cambian, sin borrar nada.
Segunda. Un cambio de permisos demasiado amplio alcanzó el directorio de datos de una base en el entorno de pruebas, que existe precisamente para eso. Se detectó en el mismo minuto por el registro del servicio, se restauró el estado correcto y el script quedó acotado a los tres ficheros que realmente copia. Ese es el recorrido completo de un fallo cuando pruebas va primero: minutos, y ningún cliente enterado.
Tercera, y es la que importa. El despliegue a producción abortó solo: el respaldo previo a la migración no pudo verificarse contra el almacenamiento externo, y mi regla —nacida de una caída de doce horas en septiembre— dice que sin respaldo verificado no hay migración. El candado hizo exactamente lo que se le pide, y producción no se tocó.
Lo que señalaba ese aborto no era un fallo del día, sino una costura: semanas antes había retirado a propósito uno de los destinos de copia, y dos scripts seguían exigiéndolo e intentándolo primero. Nada que un escáner de vulnerabilidades, una auditoría de dependencias o un aviso de seguridad pueda encontrar jamás: lo encontró una regla propia que prefiere plantarse antes que seguir a ciegas.
Reescribí la cadena entera —destino principal obligatorio y primero, el secundario opcional, los archivos de clientes a un remoto cifrado— y la probé con una corrida real antes de volver a desplegar: 0 errores, 42 de 42 tablas, 285 MB cifrados subidos y verificados en siete minutos. Después, el despliegue pasó.
Un despliegue que se niega a correr no es un fallo del despliegue. Es la única alarma que no se puede ignorar.
Hubo una cuarta, menor y reveladora: dos entornos con repositorios, candados y ejecutores separados seguían compartiendo el demonio de Docker. Dos fusiones con dos minutos de diferencia los pusieron a construir imágenes a la vez y producción abortó con un error interno del almacén de capas. No se tocó nada —abortar antes de recrear es la regla—, se relanzó y entró en minuto y medio. El arreglo de fondo fue un candado común: el segundo espera al primero en lugar de romperse. El aislamiento entre entornos tiene un suelo, y lo que se comparte necesita su propio candado.
Al final de todo, la actualización del sistema: 46 paquetes, Docker y containerd incluidos, con reinicio inevitable del demonio. Los 63 contenedores volvieron solos en cinco segundos y las siete URL públicas respondieron desde fuera inmediatamente después. Eso no fue suerte: fue una política de reinicio decidida en frío meses antes, que ese día se cobró.
415 bien puesto cerró una ruta de ejecución remota en todos los productos mientras se reconstruían las imágenes.Cierre en cifras, que es como me gusta medir una semana: 124 avisos revisados · 13 verificados de forma adversarial · 13 pull requests · 7 productos desplegados · 46 paquetes de sistema actualizados · 1 cadena de respaldo reescrita y probada con datos reales · 1 incidente, en pruebas, resuelto en minutos · 0 intrusiones · 0 caídas en producción más allá de los segundos que tarda un contenedor en recrearse.
Quedan tareas programadas, como en cualquier sistema vivo: claves por rotar, cabeceras que endurecer en el borde, un framework que migrar antes de que termine su soporte. Están escritas, con fecha, y la revisión se repite cada mes. Esa es la diferencia entre una lista de pendientes y una deuda que te explota.
La pregunta con la que cierro es la misma que le hago a cualquiera que me enseñe su infraestructura:
Si mañana sale un fallo crítico en algo que tú no escribiste y que está debajo de tu aplicación, ¿cuánto tardas en saber si te afecta, y quién lo aplica?
Si nadie puede dibujarte esa cadena en una servilleta, probablemente haya un puerto abierto que no sabes que existe. Operar software propio y ajeno es parte de lo que hago.