Uso opencode todos los días, con agentes que me escriben código, me administran el sistema y me ayudan con el lab. En algún momento me di cuenta de que el agente podía hacer casi todo, menos una cosa: correr herramientas de pentesting.

Podía ejecutar nmap desde el host, claro. Pero eso ensuciaba mi Arch, instalaba dependencias que no quería, y sobre todo: darle a un agente de IA acceso directo a mi sistema completo para correr herramientas ofensivas me parecía una idea horrible. Necesitaba un entorno aislado, reproducible, donde el agente tuviera las herramientas pero sin tocar nada del host. Así nació HACKER-MCP-COMMANDER, un MCP server con 35 herramientas de pentesting corriendo en un contenedor Kali Linux. Aclaro desde el arranque: es un work in progress. Lo que cuento acá son los primeros pasos de un proyecto que armé para optimizar mi trabajo y mi aprendizaje, no una herramienta terminada.

El problema: agentes con manos pero sin herramientas

El protocolo MCP (Model Context Protocol) le da a los asistentes de IA acceso a herramientas externas de forma estandarizada. opencode, Claude Desktop, Cursor, todos lo soportan. La parte difícil no era el protocolo, era decidir dónde corren las herramientas.

Las opciones que evalué: un server MCP en el host que hablara con la Docker API (como hace k3nn3dy-ai/kali-mcp), un bridge hacia una API Flask dentro del contenedor (el enfoque del paquete oficial mcp-kali-server), o meter el server MCP entero dentro del contenedor Kali. Elegí la tercera. El host solo necesita Docker y un wrapper de 20 líneas que hace docker start + docker exec -i. Todo lo demás vive adentro del contenedor.

La arquitectura

El flujo es simple: el cliente MCP (opencode) habla por stdio con el wrapper, el wrapper ejecuta el server FastMCP dentro del contenedor, y el server lanza las herramientas como subprocesos. El contenedor corre Kali rolling con unas cuarenta herramientas instaladas: nmap, masscan, gobuster, ffuf, sqlmap, hydra, testssl.sh, wpscan, searchsploit, enum4linux-ng, entre otras.

opencode ──stdio──► kali-mcp.sh ──docker exec──► FastMCP server ──subprocess──► nmap, sqlmap, hydra...
                    (wrapper host)                    (dentro del contenedor)

Cada engagement de pentesting es una sesión con su target autorizado, historial de comandos y evidencia. Los resultados de los scans se guardan en /workspace/sessions/<id>/, un volumen compartido que es el único punto de contacto entre el contenedor y el host. El wrapper tiene cold start: si el contenedor está apagado, lo levanta solo.

Seguridad por diseño

Un agente de IA con herramientas ofensivas pide a gritos una capa de contención. La lista de lo que hice, en orden de importancia:

  • Allowlist de binarios: el server solo ejecuta comandos de una lista configurable. No hay shell intermedia, shlex.split parsea los argumentos y los metacharacters (;, &, |, backticks) se rechazan.
  • Blacklist dura: rm -rf /, mkfs, dd if=, shutdown y similares están bloqueados siempre, sin excepción.
  • Usuario no-root: el contenedor corre como pentester, con sudo restringido. nmap, masscan y tcpdump tienen file capabilities (setcap) para raw sockets.
  • Red aislada: bridge pentest-net, sin --privileged, sin host network, solo las capabilities NET_RAW y NET_ADMIN.
  • Timeouts y límites: 120 segundos por comando por defecto y 100 KB de output máximo, para que un scan no te vuele el contexto del agente.

El resultado: el agente puede escanear, enumerar y atacar desde adentro del contenedor, pero no puede tocar el host, no puede escalar a root y no puede correr nada que no esté en la lista.

Los bugs que me despertaron

El proyecto se hizo en un día, pero ese día tuvo momentos. Algunos bugs fueron clásicos de integración, otros me enseñaron algo que no estaba en ningún tutorial.

El más raro: no-new-privileges: true en el compose. Suena a buena práctica de hardening, y lo es, pero hace que el kernel ignore las file capabilities. Resultado: nmap corría como pentester y fallaba con Couldn't open a raw socket. Operation not permitted. El diseño del proyecto dependía de setcap + usuario no-root, y esa opción lo rompía todo. Lo saqué con un comentario explicando por qué, y el raw socket volvió a funcionar.

Otro que me hizo perder tiempo: ssl_analysis se colgaba contra hosts sin TLS. testssl.sh detecta que el puerto no responde TLS y pregunta Really proceed? esperando input. El server esperaba la respuesta hasta el timeout. La solución fue testssl --quiet --warnings off. La opción -w que había visto en algún blog no existe en testssl 3.2.2.

Y el clásico de siempre: la imagen Docker quedó desincronizada con el repo. Había corregido un bug en creds.py (FastMCP 3.x no acepta **kwargs en las tools) pero nunca reconstruí la imagen. El server no arrancaba con un error que ya había arreglado. La lección: después de tocar el código, make build siempre.

También hubo sorpresas de ecosistema: waybackurls no existe como paquete apt de Kali (hay que instalarlo con Go), y hexdump vive en bsdmainutils. Detalles que solo aparecen cuando construís la imagen y el build falla.

La prueba real: scanme.nmap.org

Para verificar el flujo completo usé scanme.nmap.org, el host de prueba que el proyecto Nmap autoriza explícitamente para escaneos. Creé la sesión, corrí el port scan, enumeración DNS, análisis de headers, gobuster y testssl. Todo desde el agente, sin tocar una terminal.

Los resultados fueron interesantes: 22 SSH con OpenSSH 6.6.1p1, 80 con Apache 2.4.7 (desactualizado, con 0 de 7 security headers), y unos 90 puertos tcpwrapped que huelen a honeypot. El scan completo tardó 203 segundos, el -sV contra puertos tcpwrapped es lento. gobuster encontró .svn/, images/ y shared/. La evidencia quedó guardada en el workspace de la sesión, visible desde el host.

El momento en que vi el handshake MCP funcionar a través del wrapper, con el contenedor levantándose solo desde frío, fue el que me confirmó que la arquitectura estaba bien. El agente no sabía ni le importaba que las herramientas corrieran en un contenedor.

Lo que aprendí

Tres cosas. La primera: la contención no se negocia. Un agente con herramientas ofensivas sin sandbox es un incidente esperando pasar. La segunda: las opciones de hardening a veces se pisan entre sí, y el orden importa. no-new-privileges es buena práctica, pero incompatible con file capabilities. La tercera: el patrón de server MCP dentro del contenedor es mucho más simple de operar que un server en el host hablando con la Docker API. Menos piezas móviles, menos superficie.

El proyecto quedó público en GitHub con 19 tests pasando, skill incluida para opencode y documentación completa. Pero insisto: es un work in progress, lo de hoy fueron los primeros pasos. Le faltan una imagen full con Metasploit, CI, y más tools como wpscan_enum o smb_enum. La idea es que crezca conmigo, a medida que lo uso para optimizar mi trabajo diario y mi aprendizaje.

Si usás agentes de IA y te gustaría que corran herramientas de pentesting sin ensuciar tu sistema, el repo está ahí para copiarlo, criticarlo o mejorarlo. El contenedor Kali ya está armado, solo falta el target autorizado.

Referencias

Uso bajo consenso. Este proyecto ejecuta herramientas ofensivas. Solo operá contra sistemas que poseas o tengas autorización explícita para testear. No se permite el uso de esta ni de ninguna de mis herramientas para realizar actividades ilegales.