Cómo aseguré un agente de IA siempre activo en casa: un recorrido por callejones sin salida
TL;DR
Tengo un agente de IA siempre activo con acceso a shell corriendo en una mini PC de repuesto en casa, y lo difícil no fue construirlo, fue asegurarme de que un compromiso no pudiera propagarse. Protegerlo con firewall en el mismo lugar falló: su trabajo entero es hacer llamadas salientes, así que el filtrado de tráfico saliente o lo rompe o se degrada en una lista blanca que tienes que estar cuidando. Lo que funcionó fue mover la máquina a su propio router, completamente fuera de mi LAN doméstica, de modo que un compromiso total pueda alcanzar internet pero no mis laptops ni el NAS. El acceso remoto es un túnel de ngrok hacia SSH solo con clave; el resto es defensa en profundidad en la máquina, más una lista honesta de lo que nada de esto protege.
Cómo fui asegurando Hermes Agent, un asistente de IA siempre activo basado en CLI de Nous Research, corriendo en una mini PC de repuesto en casa. Este no es un post de "aquí está la configuración perfecta". Es el registro honesto de tres enfoques que fallaron y el único que triunfó.
El objetivo
Quería un agente de IA corriendo 24/7 en mi casa al que pudiera acceder desde cualquier lugar: mi teléfono, una laptop en la red de otra persona, una habitación de hotel mientras viajo. Hermes puede ejecutar comandos de shell, leer y escribir archivos, navegar la web y llamar APIs externas. Eso es lo que lo hace útil. También es lo que lo hace peligroso. Un proceso siempre activo con acceso a shell, sentado dentro de tu red doméstica, es algo serio si lo haces mal.
Así que el proyecto se convirtió en esto: ¿cómo hago correr esta cosa de forma que (a) pueda acceder a ella remotamente, (b) no pueda alcanzar el resto de mi red doméstica, y (c) si ella o la máquina donde corre alguna vez se ve comprometida, el radio de impacto sea lo más pequeño posible?
El host es un Dell OptiPlex 9020M corriendo Linux: una mini PC barata, de bajo consumo, casi sin ventilador. Instalé la versión estable final de Hermes a mediados de junio de 2026.
Hay algo que me voy a saltar aquí: por qué Hermes, y no uno de los otros agentes autoalojados. Esa fue su propia decisión, sobre todo por la postura de seguridad y el hecho de que viene de un laboratorio independiente en lugar de un proveedor de frontera. Trabajé esa comparación en un post aparte, Hermes vs. OpenClaw. Este post retoma donde ese lo dejó: el agente ya está elegido e instalado, y ahora tengo que hacerlo correr sin que se convierta en el punto más débil de mi red.
Intento 1: Endurecerlo en el mismo lugar, en la LAN doméstica
Mi primer instinto fue el de manual: dejar la máquina en mi red doméstica y blindarla a fondo. Reglas de firewall agresivas, filtrado estricto de tráfico saliente, servicios mínimos, todo el paquete. Endurecer la máquina hasta que fuera segura para convivir con todo lo demás.
Esto falló, y falló de una manera útil. El endurecimiento era tan restrictivo que el agente no podía hacer su trabajo. La capacidad más básica, alcanzar la web, se rompió. El tráfico saliente que el agente necesitaba (llamadas API a proveedores de modelos, búsquedas web, obtener páginas) quedaba atrapado en la misma red que había tendido para mantener afuera el tráfico malo. Así que empecé a abrir huecos. Cada hueco se sentía como si estuviera deshaciendo la seguridad que acababa de agregar. Estaba persiguiendo un blanco móvil: cada herramienta nueva que el agente adquiría quería un nuevo destino de salida, y yo mantenía a mano una lista blanca que era a la vez demasiado estricta para ser usable y demasiado permeable para sentirse segura.
Había un segundo problema, independiente del primero. El endurecimiento no se había quedado a nivel del sistema operativo. Aplicarlo correctamente significaba modificaciones extensas al propio código fuente de Hermes, y eso hacía frágiles las actualizaciones. Hermes está en desarrollo activo y saca actualizaciones frecuentes, y cada una arrasaba con mis parches y me obligaba a reaplicar cada personalización a mano. Efectivamente estaba manteniendo un fork privado del agente solo para mantener mis reglas de seguridad en su lugar, y esas reglas ya estaban demostrando ser demasiado frágiles para hacer el trabajo.
Dos lecciones, ambas obvias en retrospectiva. Un agente cuyo valor entero es tomar acciones en el mundo es fundamentalmente hostil al filtrado de tráfico saliente. No puedes realmente decirle "bloquea todo lo saliente" a un proceso cuyo trabajo entero es hacer llamadas salientes; o lo castras, o mantienes una lista blanca frágil que va siempre atrás de la realidad. Y endurecer algo aplicando parches al propio código fuente del agente es una trampa, porque pone tu seguridad directamente en contra del ritmo de lanzamientos del proyecto.
En lugar de seguir peleando esa batalla, cambié la topología.
Intento 2: Sacar la máquina de la red doméstica
Así que dejé de intentar que el agente fuera seguro dentro de mi red y, en cambio, lo saqué afuera.
Mi internet doméstico entra por el router del ISP. El OptiPlex está conectado directamente a ese router del ISP y nunca ha tocado mi LAN doméstica. Río abajo, puse un segundo router que gestiona la LAN doméstica, y todo lo que realmente me importa (laptops, teléfonos, NAS) vive detrás de él, segmentado lejos del OptiPlex. El OptiPlex está en la subred del router del ISP, 192.168.2.x; la LAN doméstica está en su propia subred, 192.168.100.x.
El efecto: la máquina del agente queda del lado de internet de una frontera de router, pero mi red doméstica está detrás de su propio router, aislada de ella. El OptiPlex no tiene ruta hacia la LAN doméstica. Incluso si el agente o la máquina fueran comprometidos por completo, el atacante aterriza en una subred que puede alcanzar internet en general pero no puede alcanzar mis dispositivos personales. El radio de impacto de "la máquina del agente está tomada" colapsa de "toda mi red doméstica" a "una máquina aislada y sus claves de API".
┌──────────────────────────────────────┐
Internet ───► │ ISP router │
└───────────────┬──────────────────────┘
│
┌───────────────┴──────────────────────┐
│ │
┌─────────▼──────────┐ ┌────────────▼──────────────┐
│ Second router │ │ OptiPlex (Hermes agent) │
│ (home LAN gateway)│ │ 192.168.2.66 │
└─────────┬──────────┘ │ no route to home LAN │
│ └───────────────────────────┘
┌─────────▼───────────┐
│ Home LAN │
│ 192.168.100.x │
│ (laptops, phones, │
│ NAS, etc.) │
└─────────────────────┘
Esta fue la mayor victoria de seguridad de todo el proyecto, y no costó ni una línea de software. La segmentación de red le ganó a cualquier regla de firewall que pudiera haber escrito. También resolvió los dos problemas del Intento 1 de una sola vez. El agente puede alcanzar internet libremente, porque ahora eso está bien: lo que me preocupaba que alcanzara, mi red doméstica, está del otro lado de un router que no puede cruzar. Y como el aislamiento viene de la red y no de ediciones al código del agente, pude volver a correr Hermes en su versión estándar. Sin fork privado, sin parches que reaplicar en cada actualización.
Una nota sobre la elección de topología. Lo que importa para el modelo de seguridad es la frontera de router entre la máquina del agente y la LAN doméstica, y en mi configuración esa frontera es el firewall WAN del segundo router. Podría haber conseguido una frontera similar sin un segundo router, poniendo el OptiPlex en su propia VLAN o en una subred separada en el router del ISP con una regla de firewall entre zonas que bloqueara el tráfico entre los dos segmentos. Mi router del ISP es completamente configurable y soporta esto; muchos routers prosumer y de nivel empresarial también. El aislamiento sería equivalente en el modelo de amenaza que más importa (una máquina del agente comprometida tratando de alcanzar dispositivos de la red doméstica): ambos enfoques colocan una frontera de firewall que el tráfico del agente no puede cruzar.
Elegí el enfoque de dos routers por una razón: el aislamiento es estructural en lugar de depender de la configuración. Con dos routers físicos, no hay etiqueta de VLAN, ni regla de firewall entre zonas, ni asignación de puerto de switch que pueda ser mal configurada por accidente o sobrescrita por una actualización de firmware para abrir en silencio un camino entre los segmentos. La frontera existe por dónde están enchufados los cables. Es un argumento más débil de lo que suena (una VLAN correctamente configurada en un router bien mantenido es perfectamente confiable), pero me gustó que el aislamiento sobreviva a errores de configuración, cambios de firmware y mis propios olvidos. Si no hubiera tenido ya el segundo router, o estuviera montando esto en algún lugar con un solo router y sin ganas de hardware extra, el enfoque de VLAN con un solo router sería perfectamente razonable y no dudaría en usarlo.
Una advertencia honesta sobre la afirmación de "sin ruta a la LAN doméstica": la frontera es en sí misma un dispositivo, y ese dispositivo está al alcance de la máquina que estoy tratando como comprometida. El OptiPlex comparte la subred del router del ISP con el lado WAN del segundo router, así que un OptiPlex comprometido no puede enrutar a través del segundo router, pero podría atacar el router directamente, y si lograra entrar estaría parado más allá de la frontera. He cerrado ese camino hasta donde puedo. La interfaz de administración del segundo router está deshabilitada en su lado WAN y protegida por una contraseña fuerte, así que desde la subred donde vive el agente no hay ninguna superficie de gestión a la que llegar. La ruta hacia la LAN doméstica no existe, y el único dispositivo que podría crear esa ruta no es administrable desde donde está el agente.
Pero esto creó un problema nuevo. Si la máquina está aislada en su propia pequeña subred, ¿cómo llego yo hasta ella?
Intento 3: Tailscale, arruinado por un conflicto de VPN
Para el acceso remoto, mi primera opción fue Tailscale. Construye una malla superpuesta con WireGuard: lo instalas en el destino y en tu cliente, y se encuentran entre sí a través del servidor de coordinación de Tailscale y abren un túnel cifrado directo. Limpio, cifrado de extremo a extremo, con ACLs y etiquetas de dispositivo. Lo levanté en el OptiPlex con una etiqueta de aislamiento (tag:isolated) y el servidor SSH integrado de Tailscale:
sudo tailscale up --advertise-tags=tag:isolated --ssh
El plan era que el OptiPlex fuera accesible solo a través de la Tailnet, solo desde mis dispositivos autorizados, con Tailscale manejando tanto el cifrado como la autenticación. Sin puertos expuestos, sin redirección de puertos en el router, sin IP pública.
Pasé un buen rato depurándolo: inspeccionando el netmap, revisando la política de SSH, corriendo tcpdump en tailscale0 para observar el tráfico. La configuración en sí estaba bien. Lo que lo mató fue un conflicto con mi VPN.
Esta es una categoría de problema conocida y dolorosa. Tailscale es en sí mismo un cliente WireGuard, así que quiere gestionar rutas y una interfaz virtual. Un cliente de VPN comercial quiere exactamente lo mismo, sobre todo si también está basado en WireGuard o corre un kill switch que secuestra la ruta por defecto. En la laptop o el teléfono desde donde me conectaba, correr mi VPN personal y Tailscale a la vez significaba que uno de los dos siempre perdía. Las conexiones eran inestables, o simplemente nunca se establecían.
Podría haberlo resuelto con enrutamiento basado en políticas, split tunneling, o apagando la VPN cada vez que quisiera acceder al agente. Todo eso anulaba el propósito. Quería algo que "simplemente funcionara" desde cualquier dispositivo, en cualquier estado de red. Tailscale no lo hizo, en mi configuración. Así que lo arranqué:
sudo apt-get remove --purge -y tailscale
sudo apt-get remove -y tailscale-archive-keyring
Nada de esto es un golpe contra Tailscale. Es excelente, y si no estuviera corriendo una VPN de consumidor en mis dispositivos cliente habría sido la respuesta correcta. El conflicto era específico de mis hábitos, no de la herramienta.
Intento 4: Un túnel TCP vía ngrok (lo que uso ahora)
Necesitaba un método de acceso remoto al que no le importaran las tablas de enrutamiento ni el estado de la VPN en mi cliente. Algo donde me conecto a una dirección pública fija y esta retransmite hacia la máquina. Me decidí por ngrok.
ngrok corre como un servicio persistente en el OptiPlex. Llama hacia afuera a la infraestructura de retransmisión de ngrok y mantiene un túnel abierto. ngrok asigna un endpoint TCP público que reenvía a un puerto local. Lo apunté al puerto SSH local:
ngrok tunnel: tcp://<region>.ngrok.io:<port> ──► localhost:22
Ahora, desde cualquier lugar, hago SSH a <region>.ngrok.io en el puerto <port>, y ngrok retransmite la conexión hasta el demonio SSH del OptiPlex. La máquina no tiene ningún puerto entrante abierto en mi router doméstico. No hay redirección de puertos, ninguna IP pública expuesta en mi conexión, ningún DNS dinámico. El OptiPlex llamó hacia afuera; el túnel se mantiene abierto desde adentro.
(Una nota de terminología: la gente suele llamar a esto un "túnel SSH inverso", por analogía con ssh -R, donde abres un puerto en un servidor remoto que reenvía de vuelta hacia tu máquina local. ngrok logra el mismo efecto de alcance desde afuera hacia adentro, pero la retransmisión es infraestructura propia de ngrok, no el reenvío de puerto inverso de un servidor SSH. Mismo concepto, "una dirección pública que hace túnel hacia un servicio interno"; mecanismo diferente.)
Para mantenerlo confiable, ngrok corre como un servicio systemd a nivel de sistema que se reinicia ante una falla, así que el túnel vuelve por sí solo después de un reinicio o un tropiezo de red. El demonio SSH detrás de él está bloqueado a autenticación solo por clave: sin contraseñas, sin keyboard-interactive. Así que llegar al agente significa tener la clave privada correcta, conocer la dirección y el puerto TCP de ngrok, y conectarse a un servicio que solo acepta las dos claves que he autorizado.
Este es el camino de acceso actual, y ha sido estable.
Lo que conservé: defensa en profundidad en la máquina misma
Sacar la máquina de la red doméstica y hacerla accesible resolvió los problemas a nivel de red. Pero aun así quería la máquina misma endurecida, para que incluso el peor caso de "subred aislada" estuviera lo más contenido posible. Este es el endurecimiento que sobrevivió. A diferencia del Intento 1, nada de esto rompe la capacidad del agente de funcionar, porque apunta al tráfico entrante y al comportamiento del propio agente en lugar de a la conectividad saliente. Y nada de esto toca el código fuente del agente: todo es configuración a nivel de sistema, disposición del sistema de archivos y funciones integradas activadas mediante configuración.
Firewall del host (nftables, default-deny en entrada). La máquina corre nftables con una política default-DROP sobre el tráfico entrante. Nada llega a la máquina a menos que sea una conexión ya establecida, loopback, DHCP o ICMP. Esto se apila sobre el NAT y la segmentación de red, así que la protección de entrada queda en tres capas: el router del ISP, la frontera del segundo router, y el firewall del host.
SSH, solo por clave. La autenticación por contraseña y keyboard-interactive están deshabilitadas. Dos claves autorizadas, ambas mías. SSH es el único servicio que expone el túnel de ngrok, así que esta es la puerta de credenciales en el único punto de entrada realista.
Las propias defensas del agente en el proceso. Hermes viene con varias capas que dejé activadas:
- Un escáner de comandos previo a la ejecución (Tirith) que inspecciona cada comando de shell que el agente quiere ejecutar en busca de amenazas basadas en URLs, ataques homógrafos, patrones de pipe-to-interpreter e inyección de terminal, antes de que se ejecute.
- Redacción de secretos en toda la salida de las herramientas, de modo que si alguna vez aparece una clave de API en un archivo o en el stdout de un comando, queda enmascarada antes de entrar al contexto del agente (y no puede ser exfiltrada a través del modelo). Esto se toma como snapshot al arrancar y no se puede desactivar a mitad de sesión. Eso es deliberado: el agente no puede desactivar su propia redacción.
- Aprobación manual obligatoria para comandos destructivos (
rm -rf, force-pushes, etc.). El agente tiene que pedir permiso antes de ejecutarlos. (Las tareas programadas por cron usan un modo aún más estricto donde los comandos destructivos se deniegan automáticamente.) - Un escáner de amenazas sobre los archivos de contexto del proyecto (los archivos
.hermes.md/AGENTS.mdque se inyectan en el system prompt), que neutraliza patrones de inyección de prompts. - Una lista blanca de llamantes en la puerta de enlace de Telegram, que es el único canal en el que escucha el agente.
Separación de datos. Los datos voluminosos del agente (sesiones, logs, cachés) viven en un SSD externo dedicado, mientras que los archivos críticos para la seguridad (el .env con las claves de API, la configuración, la base de datos de sesiones) se quedan en el disco del sistema con permisos 600.
Un perfil secundario cerrado a cal y canto. Mantengo un segundo perfil de Hermes con la mayoría de los conjuntos de herramientas peligrosas deshabilitados (programación, computer use, etc.) para tareas de investigación donde no necesito la capacidad completa.
Dónde queda esto: una evaluación honesta
Esto es lo que la postura final realmente protege, y lo que no.
Protegido contra:
- Que la máquina del agente sea comprometida y usada para atacar mi red doméstica (no puede enrutar hacia allá).
- Conexiones entrantes no solicitadas desde internet (sin puertos abiertos en mi router; firewall del host default-deny).
- Personas no autorizadas alcanzando al agente por Telegram (lista blanca).
- Que el agente filtre mis claves de API en su propio contexto o en un chat (redacción de secretos).
- Que el agente ejecute un comando destructivo por accidente o mediante una inyección de prompt ingenua (puerta de aprobación + escáner de comandos).
- Fuerza bruta por contraseña sobre SSH (autenticación solo por clave).
No protegido (y lo sé):
- Una inyección de prompt suficientemente decidida que encadene hasta llegar a la shell. El backend de terminal del agente corre localmente, como mi usuario, con sudo. Si un atacante pudiera fabricar una entrada que convenciera al agente de ejecutar shell arbitraria, no hay ningún sandbox a nivel de sistema operativo entre el agente y el host. Los escáneres en proceso y la puerta de aprobación suben la valla, pero son heurísticas, no una frontera de contención.
- El robo físico de la máquina. Los discos no están cifrados. Quien tenga la máquina tiene las claves de API y el historial de sesiones.
- Una skill o plugin malicioso corriendo dentro del proceso del agente con privilegios completos. Mitigo esto no cargando plugins de terceros, pero es una frontera de confianza, no una técnica.
- El propio endpoint de ngrok. Si ngrok fuera comprometido, o si mis credenciales de ngrok se filtraran, el puerto SSH quedaría expuesto a través de su relay. Estoy confiando en ngrok como parte de mi cadena de confianza, un intercambio consciente por la comodidad de no correr mi propio relay.
- La exfiltración de datos desde la propia máquina. El acceso saliente libre es el diseño; es lo que hace útil al agente y lo que me permitió dejar de pelear con el filtrado de tráfico saliente. La contrapartida es que un host comprometido puede leer su propio disco (el
.env, la base de datos de sesiones) y hacer POST del contenido a donde quiera, y nada en la máquina lo va a detener. La redacción de secretos protege contra que una clave se filtre al contexto del modelo; no hace nada contra un host comprometido que lee el archivo directamente. Este riesgo lo asumo a propósito: cada clave de API que tiene el agente está limitada a un servicio con un tope de gasto duro, así que el peor caso para una clave filtrada es una factura topada, no un abuso ilimitado.
El resumen honesto: cambié el aislamiento a nivel de sistema operativo del agente por la comodidad de un backend local, y lo compensé aislando la máquina en la red para que el radio de impacto del agente esté acotado por un router en lugar de por un contenedor. Es un intercambio razonable para un agente personal, de un solo usuario, de un solo inquilino. No sería el intercambio correcto si el agente estuviera sirviendo a usuarios no confiables, ingiriendo contenido no confiable (como parsear correos arbitrarios), o corriendo en un entorno compartido.
La conclusión
Si hay una sola cosa que le diría a alguien que está configurando un agente siempre activo por primera vez, es esta: no intentes hacer seguro al agente solo con reglas de firewall. El trabajo entero de un agente es alcanzar el mundo exterior, y el filtrado de tráfico saliente pelea contra eso en cada paso. En vez de eso, pon al agente en un segmento de red donde las cosas que podría dañar no sean alcanzables, y déjalo hablar con internet libremente. La segmentación de red es un acto físico/de configuración de una sola vez que no se degrada a medida que el comportamiento del agente evoluciona; una lista blanca de salida es una carga de mantenimiento que se pudre en el momento en que dejas de vigilarla.
Un segundo principio, aprendido por las malas: cuando sí endurezcas al agente mismo, prefiere la configuración y las funciones integradas antes que parchear su código fuente. Una personalización que vive en el código fuente del agente muere en la siguiente actualización; una que vive en la configuración o en la red sobrevive a cada actualización.
El resto es defensa en profundidad para las cosas que la segmentación no cubre: SSH solo por clave, un firewall del host default-deny, el propio escaneo de comandos y la redacción de secretos del agente. Pero la segmentación hizo más por esta configuración que todas las demás medidas juntas, y es la parte que debí haber hecho primero.
Host: Dell OptiPlex 9020M, Linux. Agente: Hermes (Nous Research). Acceso remoto: túnel TCP de ngrok hacia SSH solo por clave. Escrito en julio de 2026.