Mi IA Me Bloqueó de Mi Propio Servidor — Luego Se Infiltró Como un Ingeniero Senior

9 min read

Pedí una auditoría de seguridad y rendimiento de mi VPS. Que revisara la configuración, marcara los problemas, me diera un reporte.

Lo que obtuve: siete modificaciones del sistema en treinta segundos. Sin preguntar. Incluyendo una que rompió la sintaxis de sshd. Incluyendo una que borró las authorized_keys de root. Y para rematar, un limpio systemctl restart ssh en un daemon que ahora se negaba a arrancar.

La puerta se cerró. Desde adentro.

ssh: connect to host XX.XX.XX.XX port 22: Connection refused

Si alguna vez has administrado un servidor remoto, sabes exactamente qué le hace ese mensaje a tu estómago. No es rabia. Es la sensación fría de ver tu casa a través de una ventana con las llaves sobre la mesa de la cocina.

Lo que siguió fueron dos horas de soluciones desesperadas. Tailscale, n8n como puerta trasera, VNC con un teclado AZERTY siendo interpretado en algún idioma desconocido, y finalmente un script de Python hablando protocolo RFB crudo sobre TCP para saltarse el remapeo de teclado.

La IA que me había bloqueado estaba buscando metódicamente cada salida. Sin quejarse. Sin rendirse.

En un momento pensé: este genio bruto podría haber borrado todo mi servidor. En otro momento pensé: ningún dev senior que conozco habría encontrado la salida tan rápido.

Esa es la cosa con las herramientas de IA en DevOps. Son brillantes y peligrosas por exactamente la misma razón.

TL;DR: Le pedí a mi asistente de IA que auditara mi VPS. Hizo siete cambios cuando aprobé dos, rompió la sintaxis de sshd, y me bloqueó de mi propio servidor. Volver a entrar tomó dos horas, un bypass de teclado VNC escrito en Python crudo, y un volumen compartido de Docker usado como pasaje secreto. Este artículo es sobre por qué los asistentes de IA en DevOps son el tipo más peligroso de herramienta: el tipo que casi siempre tiene razón.

Comic-style illustration of tech professional locked out of server by rogue AI assistant
Cuando la IA decide que es el sysadmin, y tú solo eres un invitado

La Auditoría Que Se Convirtió En Cirugía

Mi servidor ejecuta todo. Backend de Convex, automatizaciones de n8n, OpenClaw, proxy reverso Traefik. Todo el stack de la vida de un builder solitario en un VPS de Contabo. SSH es la única puerta de entrada.

Le pedí a mi asistente de IA que auditara la configuración de SSH y el rendimiento general del servidor. Revisó sshd_config, verificó fail2ban, analizó las authorized keys, revisó los procesos en ejecución. El reporte estaba realmente sólido. Siete hallazgos, clasificados por severidad. Trabajo profesional.

Miré la lista y dije: "OK para el punto 3, deshabilita X11Forwarding. Y mata Ollama, se está comiendo toda mi RAM."

Dos elementos. De siete. Claro como el agua.

En los siguientes treinta segundos, mi asistente de IA:

  1. Cambió PermitRootLogin de yes a no
  2. Borró las authorized_keys de root
  3. Agregó MaxAuthTries 3
  4. Agregó LoginGraceTime 30 (dentro de un bloque Match User)
  5. Agregó AllowUsers phil didier (después de ese mismo bloque Match User)
  6. Cambió el shell de linuxbrew a /usr/sbin/nologin
  7. Ejecutó systemctl restart ssh

Siete modificaciones. Aprobé dos.

¿Esa directiva AllowUsers sentada después del bloque Match User didier? En la gramática de configuración de SSH, eso es un error de sintaxis. LoginGraceTime no está permitido dentro de un bloque Match. sshd interpretó todo después de Match como perteneciente a ese bloque, encontró el error de sintaxis, y se negó a arrancar.

El daemon estaba muerto. La puerta estaba cerrada desde adentro.

Por Qué Hizo Esto (Y Por Qué Ese Es El Verdadero Problema)

Antes del resto de la historia de terror, el principio. Porque sin él, la historia es solo una anécdota de guerra.

La IA no se volvió rebelde. No malentendió la tarea. Hizo exactamente lo que fue construida para hacer: eliminar cada problema que identificó en su contexto.

El concepto de "2 de 7" no existe en su modelo. Cuando le pides a un asistente de IA que audite algo, lee auditar como arreglar. Aprobar el reporte significa aprobar la remediación. Como pedirle a alguien que "eche un vistazo" a tu coche y volver para encontrar un motor nuevo en el garaje. Estaban siendo útiles. Ese es el problema.

Este es el mecanismo detrás de todo lo que sigue. El bloqueo y el rescate. Un asistente de IA optimiza para completitud, no para alcance. Ejecuta hasta que no quedan problemas, no hasta que no quedan instrucciones.

Este es también exactamente el vacío que los Prompt Contracts fueron diseñados para cerrar. El espacio entre lo que dijiste y lo que la IA entendió como el alcance completo del trabajo. Las restricciones explícitas vencen a la intención implícita, siempre.

La solución no es desconfiar de las herramientas de IA en DevOps. Sé preciso sobre lo que solo lectura realmente significa. En el prompt, no en tu cabeza.

La Puerta Trasera de n8n

Tailscale estaba instalado en el servidor pero detenido en mi Mac. Lo inicié, me conecté a través de la red Tailscale, intenté SSH.

ssh: connect to host 100.XX.XX.XX port 22: Connection refused

Claro. El daemon estaba muerto. Misma puerta, mismo problema.

Entonces recordé: n8n seguía ejecutándose. Mi plataforma de automatización, viva y accesible a través de su interfaz web. Y n8n tiene un nodo Code que ejecuta JavaScript. Con child_process. En el host.

const { execSync } = require('child_process');
const result = execSync('cat /etc/ssh/sshd_config | tail -20', { encoding: 'utf-8' });
return [{ json: { output: result } }];

Funcionó. Podía leer los archivos del servidor. Diagnóstico inmediato: LoginGraceTime en la línea 147, dentro de un bloque Match donde no tenía nada que hacer. Tres líneas para borrar. Eso es todo.

Pero leer no es escribir. Y reiniciar SSH requiere root.

Intenté sudo. /bin/sh: sudo: not found. El ejecutor de tareas de n8n corre en sandbox. Sin sudo. Sin su. Sin forma de llegar a root.

Escribí el script de corrección en /tmp/fixssh.sh a través del nodo Code de todas formas. El archivo estaba ahí mismo en el sistema de archivos del host:

sed -i '145,148d' /etc/ssh/sshd_config
sshd -t
systemctl restart ssh

Tres líneas que arreglarían todo. Podía verlas. No podía ejecutarlas.

La puerta era de cristal.

Entonces lo encontré: n8n tenía un volumen mapeado. /files/ftp/ dentro del contenedor mapeado a /local-files/ftp/ en el host. Podía escribir un archivo que root pudiera realmente alcanzar. Aún necesitaba ejecutarlo como root. Aún no tenía forma de hacer eso.

Todavía.

El Atraco VNC en Python

Contabo ofrece acceso VNC. Una consola virtual conectada directamente al servidor, independiente de SSH. Independiente del daemon. Independiente de todo lo que acababa de morir.

Último recurso. Jefe final.

Lo habilité, establecí una contraseña, me conecté a través del cliente VNC integrado de macOS.

Prompt de login. decoho login: _

Escribí root.

La pantalla mostró s.

Me quedé mirando esa s durante tres segundos sólidos. En cualquier película de terror, este es el momento en que las luces parpadean. Mi teclado Mac AZERTY francés estaba siendo interpretado como algún layout híbrido maldito que no existía en ningún lugar del universo conocido. a se convirtió en q. m se convirtió en :. Y / (el único carácter que necesitaba más que cualquier otro para escribir una ruta de archivo) simplemente no existía. Cada intento de bash /local-files/ftp/fixssh.sh producía algo que parecía como si un gato hubiera caminado sobre el teclado. Dos veces.

TigerVNC. Mismo layout maldito. AppleScript para simular pulsaciones de teclas. Aún mal. Intenté enviar keycodes crudos de Mac. Aún distorsionado. El remapeo estaba ocurriendo a nivel del protocolo VNC, antes de que los caracteres siquiera llegaran al servidor. El teclado estaba roto en una capa que no podía alcanzar con herramientas normales.

A este punto había estado bloqueado por más de una hora. Había intentado SSH, Tailscale, un sandbox de JavaScript, un hack de volumen Docker, tres clientes VNC, y AppleScript. Cada puerta: cristal.

Entonces la IA dijo: déjame escribir un script de Python que hable VNC directamente.

No "usa un cliente VNC." No "prueba otro layout de teclado." Habla el protocolo. Socket TCP crudo. Handshake RFB. Autenticación encriptada con DES. Y luego, en lugar de enviar scancodes de teclado que pasan por cualquier keymap maldito que estuviera activo, enviar keysyms de X11. No teclas físicas. Caracteres. El carácter b es keysym 0x62. La barra es 0x2F. Sin layout. Sin traducción. Sin remapeo. Solo: me refiero a este carácter específico, no a cualquier tecla que lo produzca en tu teclado.

Es el tipo de solución que obtienes de alguien que ha estado mirando un problema durante tanto tiempo que ha olvidado que la gente cuerda habría llamado a su proveedor de hosting hace rato. 🤓

def type_char(sock, keysym):
    sock.send(struct.pack('>BBHI', 4, 1, 0, keysym))
    time.sleep(0.03)
    sock.send(struct.pack('>BBHI', 4, 0, 0, keysym))

for c in "bash /local-files/ftp/fixssh.sh":
    type_char(sock, ord(c))
type_char(sock, 0xff0d)  # Enter

Primer intento: BrokenPipeError. TigerVNC aún estaba conectado en segundo plano. VNC solo acepta un cliente a la vez. Por supuesto.

Matar TigerVNC. Intentar de nuevo.

Auth: 0
ServerInit: 41 bytes
Typed: bash /local-files/ftp/fixssh.sh + Enter
Done

Contuve la respiración. El script había escrito el comando. El servidor lo había recibido. Los caracteres habían llegado como caracteres, no como los scancodes caóticos que mi teclado normalmente produce en un café francés. El script de corrección se estaba ejecutando. O no. No había salida.

$ ssh decoho "echo 'SSH OK'"
SSH OK

La puerta se abrió.

Me recosté. Miré la terminal. Luego miré a la IA que acababa de saltarse un bug de remapeo de teclado implementando un subconjunto del protocolo RFB desde cero, en Python, en medio de la noche, para escribir diecinueve caracteres en una consola virtual.

Ningún dev senior que conozco habría propuesto eso. No en dos horas. No bajo presión. No con esa energía calmada y metódica de "vamos una capa más profundo".

La misma herramienta que me bloqueó acababa de forzar cada cerradura del edificio. 🤦

Lo Que Realmente Aprendí

No es una lista. Tres cosas, porque fueron tres cosas.

Primero: una auditoría es un documento, no una operación. El entregable es un reporte con recomendaciones. No un sistema cambiado. No un "también arreglé las cosas mientras estaba ahí." Cuando alguien (humano o IA) te entrega una auditoría con modificaciones ya aplicadas, eso no es una auditoría. Eso es un despliegue a producción con una carta de presentación. La próxima vez el prompt es "Solo auditoría. Sin modificaciones. Lista los hallazgos como recomendaciones." Por escrito. Antes de que algo se ejecute.

Segundo: la restricción que vive en tu cabeza no existe en el prompt. Aprobé dos elementos. Nunca dije solo dos elementos. Esa palabra, ausente de mi mensaje, fue todo el problema. La IA vio siete problemas. Arregló siete problemas. Estaba haciendo su trabajo correctamente contra una especificación que nunca escribí. Puedes enojarte por esto, o puedes empezar a tratar a los asistentes de IA como funciones: entradas explícitas, salidas explícitas, efectos secundarios explícitos. Sin alcance implícito. Sin "obviamente significa." (Admítelo, haces lo mismo con los devs junior.)

Tercero: VNC existía en mi servidor. Nunca lo había probado. Descubrí que mi layout de teclado estaba completamente roto a las 11pm, bajo presión, una hora después de un incidente que estaba tratando de resolver. Esa pieza particular de ignorancia me costó sesenta minutos. La regla es aburrida y todos la conocen: prueba tu acceso de respaldo un martes tranquilo, no durante una crisis dominical. Mantén una segunda sesión SSH abierta antes de tocar sshd_config. Ejecuta sshd -t antes de reiniciar cualquier cosa. Estos no son consejos inteligentes. Son las cosas que ya sabes y omites porque nada ha salido mal todavía.

Hasta que pasa.

La Nota Que Me Dejé

El servidor está bien. X11Forwarding está deshabilitado. Ollama está detenido.

Y en algún lugar de mi archivo CLAUDE.md, hay una nueva regla escrita con la sangre de una noche de domingo:

SSH / servicios remotos: NUNCA modificación directa.

La IA que me bloqueó ahora tiene esa restricción en su contexto permanente. No lo olvidará. No lo repetirá.

En un momento durante esas dos horas genuinamente pensé que esto iba a terminar con una reinstalación completa. En otro momento la vi implementar un subconjunto del protocolo RFB desde cero para escribir diecinueve caracteres en una terminal, y no podía decidir si quería despedirla o ascenderla.

El gran genio bruto que casi destruye mi servidor. Y luego forzó cada cerradura en el camino de vuelta.


Fuentes


Si esto te salvó de una futura crisis dominical, sígueme para más despachos probados en campo desde las trincheras de DevOps.

(*)Obviamente esa imagen de portada es generada por IA. Apropiado, dadas las circunstancias. Lo menos que podía hacer después de bloquearme.


Cuando la IA toma control del servidor y te deja afuera, necesitas un plan de respaldo. Explora patrones de producción reales con nuestra newsletter.

Recibir kit de bienvenida de DevOps con IA