Acceso remoto seguro a agentes de IA

El látigo de largo del radio de la Tierra para hacer trabajar a los esclavos (agentes de IA)

Tengo varios agentes de IA trabajando en paralelo en mi computador con Linux (Pop!_OS). Frente al computador los manejo con herdr, al menos por ahora. Cuando me alejo, los veo y les respondo desde el celular con Collie. Todo viaja dentro de una conexión SSH que pasa por Cloudflare, y el computador no tiene ningún puerto abierto hacia la red.

Publicado el

Puertos abiertos hacia la red
Ninguno: todo escucha en 127.0.0.1
Barreras antes de tocar un agente
3: identidad, llave y dispositivo
Vías de entrada
1: SSH, también para la web

Parte 1 · La motivación

Por qué herdr

Un agente de IA para programar rinde más cuando lo dejas trabajar solo un buen rato. El problema aparece cuando tienes cinco o seis agentes abiertos en proyectos distintos: alguno termina, otro se queda esperando un permiso, y tú no te enteras hasta que revisas terminal por terminal.

herdr es un multiplexor de terminal, de la familia de tmux, pensado para agentes de IA. Un servidor en segundo plano es dueño de las terminales reales. Tú te conectas y desconectas como cliente, y los agentes siguen trabajando.

Cuando estoy frente al computador, herdr es lo que uso, al menos por el momento: lo abro en la terminal y manejo a los agentes desde ahí. Lejos del computador uso Collie, que además sirve en el escritorio como alternativa gráfica.

La idea en una línea: herdr reúne a los agentes, y Collie, el perro pastor, los vigila por ti cuando no estás frente al computador.

servidor
Es dueño de los procesos. Sobrevive a que cierres la terminal o se corte el SSH.
workspace
Uno por repositorio o tarea. Agrupa pestañas y paneles.
tab
Una vista dentro del workspace: agentes, logs, servidor.
pane
Una terminal real, dividida a lo ancho o a lo alto.
agent
Un agente de IA que herdr reconoce dentro de un panel, y del que muestra el estado.
Ventana de herdr: a la izquierda, la barra lateral con dos workspaces y la lista de agentes, uno en estado idle y otro working; a la derecha, dos agentes de IA trabajando en paneles lado a lado
herdr con dos agentes distintos trabajando en paneles lado a lado. La barra lateral muestra los workspaces, la rama de git de cada uno y el estado de cada agente. Captura del repositorio oficial de herdr (licencia Apache-2.0).

Lo que cambia en el día a día

  • Ves el estado de todos los agentes. La barra lateral muestra el estado de cada agente en todos los workspaces.
  • Nada se pierde al desconectarte. Los paneles siguen corriendo si cierras la ventana o se cae la conexión. Vuelves con herdr.
  • Se maneja con el mouse. Paneles, pestañas y bordes se pueden clicar y arrastrar, sin memorizar atajos.
  • Se puede programar. Tiene CLI y API por socket Unix, así que un script o un agente pueden manejarlo.

Los estados que importan

working blocked done idle

blocked es el que me hizo buscar acceso remoto: el agente está detenido esperando una respuesta mía. Si estoy lejos del computador, ese agente pierde horas.

Parte 2 · La interfaz

Por qué Collie

Collie es una aplicación web progresiva (PWA) que sirve mi propio computador. Lee los paneles de herdr y los convierte en una interfaz pensada para el dedo: la lista de agentes con su estado, la salida de cada uno y un cuadro para responder.

Podría entrar por SSH desde Termux y abrir herdr directamente en el teléfono. Funciona, pero con un teclado de celular, Esc, Ctrl y las flechas son un suplicio, y una interfaz de terminal no está pensada para una pantalla de seis pulgadas.

Collie no reemplaza a herdr: lo usa por debajo. Lo que escribo en Collie llega al panel de herdr, y lo que pasa en el panel se ve en Collie. Funciona con cualquier agente que corra en un panel.

Por qué una PWA y no una app nativa

  • Sin tienda de aplicaciones. La sirve mi propia máquina y se actualiza cuando actualizo Collie.
  • Se instala como una app. Queda en la pantalla de inicio y abre a pantalla completa.
  • Abre aunque la conexión tarde. Guarda en el teléfono una copia breve de lo último que vio, por hasta 24 horas y con los secretos enmascarados.
  • Avisa cuando la necesitas. Puede enviar notificaciones push cuando un agente pide una respuesta. Es opcional y se activa con collie push-keys.
  • Sin backend en la nube. La app y los datos salen de mi computador. En mi caso viajan cifrados dentro de SSH, aunque el túnel pase por Cloudflare.

Instalarla, recibir notificaciones y dictar por voz requieren un contexto seguro: HTTPS o localhost. Como entro por http://localhost:8787 a través del túnel SSH, el navegador lo trata como seguro.

Tablero de Collie: proyectos agrupados y un resumen con 1 agente que necesita respuesta, 5 trabajando, 1 terminado y 1 inactivo, seguido de la tarea de cada agente
Tablero. Todos los agentes de todos los proyectos, con el que te necesita marcado en rojo.
Un agente pide permiso para ejecutar un comando y Collie muestra las opciones como botones: Yes, Yes and don't ask again, y No
Permisos. Cuando un agente pide aprobar un comando, respondes con un toque.
Bandeja de teclas de Collie con Esc, Tab, Shift, Ctrl, Alt, flechas, Enter, Ctrl C y barra espaciadora
Teclas. Esc, Ctrl, Tab y flechas, sin pelear con el teclado del teléfono.
Respuestas rápidas de Collie: yes, no, continue, commit and push, retry y skip
Respuestas rápidas. Frases de un toque, configurables, para lo que más repites.

Capturas del repositorio oficial de Collie (licencia MIT). Muestran agentes de distintos proveedores en el mismo tablero.

Lo que puedo hacer desde Collie

responder
Un cuadro de texto normal, así que funciona el dictado del teclado del teléfono. También puedo adjuntar archivos.
ramas
Cada fila del tablero muestra la rama de git en que trabaja el agente.
cambios
La pantalla Files muestra qué archivos cambió el agente, con su diff coloreado. Es de solo lectura.
lanzar
Inicia un agente o una shell nueva en la carpeta que elija, incluso en un worktree nuevo.
historial
Lee el registro de sesión del propio agente, así que muestra el historial que la terminal no guarda.

También frente al computador

Collie no es solo para el celular. En el computador lo abro en el navegador, en http://127.0.0.1:8787, y funciona como una alternativa gráfica a la interfaz de terminal de herdr: la misma lista de agentes, con el mouse, y los cambios de código a un clic.

Hoy, frente al computador, sigo usando herdr en la terminal. Collie queda como opción cuando prefiero una vista más visual, o cuando quiero revisar qué cambió un agente sin abrir otro panel.

Aspectoherdr en la terminalCollie en el navegador
DóndeFrente al computadorEn el computador o en el celular
ControlTeclado y mouse en la terminalToque o mouse, con teclas especiales en una bandeja
Agentes que esperanEstado en la barra lateralMarca roja y contador en el tablero
Cambios de códigogit en otro panelPantalla Files con diff
Para entrarAbrir la terminalNavegador y dispositivo emparejado

Parte 3 · El requisito

Conectarme desde afuera, sin abrir la puerta

El atajo habitual es abrir el puerto 22 en el router, o publicar la web de Collie. Cualquier puerto abierto en internet recibe escaneos automáticos a todas horas. Y un notebook cambia de red: lo que escucha en todas las interfaces queda expuesto en cada Wi-Fi donde lo conectes.

Además, Collie corre con mis permisos. Quien controla un panel puede escribir comandos en mi máquina. Exponerlo mal equivale a entregar una terminal.

Lo que me puse como condición

  • Ningún puerto entrante, ni en el router ni en el notebook.
  • Verificar mi identidad antes de que el tráfico toque el computador.
  • Una sola vía de entrada, SSH, también para la web de Collie.
  • Usar la llave privada que ya tengo en el celular.

Parte 4 · La arquitectura

El recorrido de una petición

Así llega un toque en la pantalla del celular hasta un agente de IA. El orden importa: cada paso solo ocurre si el anterior dejó pasar la petición.

Celular

Termux + navegador

SSH con la llave privada; la web viaja dentro de SSH.

Borde

Cloudflare Access + Tunnel

Pide login antes de dejar pasar. El túnel lo abre el Linux hacia afuera.

Linux

Todo en 127.0.0.1

cloudflared, sshd, Collie y herdr. Nada escucha hacia la red.

En el celular
  1. 1
    Navegadorhttp://localhost:8787Abro Collie como si corriera en el propio teléfono. Para el navegador es localhost, que cuenta como contexto seguro.
  2. 2
    Termux · sshLocalForward 8787 → 127.0.0.1:8787La conexión SSH lleva dentro el tráfico web hacia Collie.
  3. 3
    cloudflared access sshProxyCommandEn vez de abrir un socket hacia un puerto, SSH viaja por un WebSocket hacia Cloudflare.
En Cloudflare
  1. 4
    Cloudflare Accesspc.tu-dominio.devPide iniciar sesión con mi correo. Sin una sesión válida, la petición termina aquí.
    Barrera 1 · identidad
  2. 5
    Cloudflare Tunnelingress → ssh://localhost:22Usa la conexión que el Linux abrió hacia afuera. El router no tiene ningún puerto abierto.
En el Linux
  1. 6
    cloudflared (servicio de systemd)→ localhost:22Entrega el tráfico en loopback. Su credencial está en /etc/cloudflared y solo root puede leerla.
  2. 7
    sshd127.0.0.1:22 · [::1]:22Solo acepta una llave Ed25519 y un usuario. Las contraseñas están desactivadas.
    Barrera 2 · llave
  3. 8
    Reenvío de puerto→ 127.0.0.1:8787sshd abre la conexión local hacia Collie en nombre del celular.
  4. 9
    Collie127.0.0.1:8787Comprueba que el host sea uno permitido y que el dispositivo esté emparejado.
    Barrera 3 · dispositivo
  5. 10
    herdrsocket Unix ~/.config/herdr/herdr.sockCollie lee y escribe en los paneles donde trabajan los agentes de IA.

Parte 5 · Defensa en profundidad

Tres barreras independientes

Cada barrera usa una credencial distinta. Para controlar un agente, alguien tendría que tener las tres al mismo tiempo.

1 · ¿Quién eres?

Cloudflare Access

Login con mi correo en el borde de Cloudflare, antes de que el tráfico entre al túnel. La sesión dura 24 horas.

Sin sesión: redirige al login y nunca aparece el banner de SSH.
2 · ¿Qué llave tienes?

OpenSSH

Solo autenticación por llave pública. Una llave Ed25519 autorizada, un usuario permitido y root bloqueado.

Sin la llave: Permission denied (publickey).
3 · ¿Qué dispositivo eres?

Emparejamiento de Collie

Un código de un solo uso, generado en el computador, deja un token en el navegador del celular.

Sin emparejar: la API responde "device not paired".
Base · red Todo escucha en loopback y UFW rechaza cualquier conexión entrante. Aunque el notebook esté en una red pública, no hay a qué conectarse.

Parte 6 · Paso a paso

La configuración

Los hostnames, el usuario y los identificadores son ejemplos. Reemplázalos por los tuyos.

1. Cloudflare Tunnel

cloudflared corre como servicio de systemd y abre una conexión saliente hacia Cloudflare. El ingress publica un solo hostname hacia SSH. Cualquier otra dirección responde 404.

La credencial del túnel vive en /etc/cloudflared con permisos 600 y dueño root. El cert.pem que deja cloudflared tunnel login no se necesita para que el túnel funcione, así que lo saqué del equipo.

/etc/cloudflared/config.yml
tunnel: <id-del-túnel>
credentials-file: /etc/cloudflared/<id-del-túnel>.json   # root:root 600
ingress:
  - hostname: pc.tu-dominio.dev
    service: ssh://localhost:22
  - service: http_status:404

2. Cloudflare Access

Es el paso que convierte el túnel en algo seguro. Sin Access, cualquiera con cloudflared llega a tu sshd: solo cambiaste el camino, no quién puede entrar.

  1. Cloudflare Zero Trust → Access → Applications → Self-hosted.
  2. Hostname público: exactamente el mismo del túnel.
  3. Política Allow · Include → Emails → tu correo.
  4. Duración de la sesión: 24 horas o menos.
Comprobación: debe redirigir al login
curl -sI https://pc.tu-dominio.dev | grep -i location
# location: https://<equipo>.cloudflareaccess.com/cdn-cgi/access/login/...

3. OpenSSH

El túnel entrega en localhost:22, así que sshd no necesita escuchar hacia afuera. Las contraseñas ya estaban desactivadas en sshd_config. El resto va en un archivo aparte para que una actualización del paquete no lo pise.

En Ubuntu 24.04, y por lo tanto en Pop!_OS, SSH arranca por activación de socket: ListenAddress recién se aplica después de regenerar el socket.

/etc/ssh/sshd_config.d/10-hardening.conf
ListenAddress 127.0.0.1
ListenAddress ::1
PermitRootLogin no
AllowUsers tu-usuario
MaxAuthTries 3
LoginGraceTime 20
X11Forwarding no
AllowAgentForwarding no
Aplicar y comprobar
sudo systemctl daemon-reload
sudo systemctl restart ssh.socket
ss -tln | grep ':22 '   # solo 127.0.0.1:22 y [::1]:22
DirectivaValorPor qué
PasswordAuthenticationnoSolo llaves. Una contraseña se puede adivinar; una llave Ed25519, no.
KbdInteractiveAuthenticationnoCierra la otra vía por la que el servidor puede pedir una contraseña.
ListenAddress127.0.0.1, ::1Solo el túnel llega a SSH. Nadie en la LAN o en el Wi-Fi del café.
PermitRootLoginnoroot nunca entra directo, ni con llave.
AllowUserstu-usuarioCualquier otra cuenta del sistema queda fuera, aunque tenga llaves.
MaxAuthTries3Corta antes a quien prueba llaves una tras otra.
LoginGraceTime20Una conexión que no se autentica se cierra a los 20 segundos.
X11ForwardingnoNo hay interfaz gráfica que reenviar desde el celular.
AllowAgentForwardingnoNadie puede usar el agente SSH del cliente a través de esta máquina.
AllowTcpForwardingyes (por defecto)Se mantiene: el reenvío hacia Collie depende de esto.

4. La red del notebook

Al revisar ss -tln aparecieron servicios escuchando en 0.0.0.0, entre ellos el correo local (Postfix), y el firewall estaba apagado. Pasé todo a loopback y encendí UFW con política de rechazo para lo entrante.

Ojo: los puertos que publica Docker con -p se saltan UFW. Si usas contenedores, publícalos en 127.0.0.1:puerto.

Postfix solo local y firewall
sudo postconf -e 'inet_interfaces = loopback-only'
sudo systemctl restart postfix
sudo ufw default deny incoming
sudo ufw enable

5. Collie

Por defecto, Collie se publica con Tailscale (tailscale serve). COLLIE_SKIP_SERVE=1 le indica que la entrada la manejas tú, en este caso con SSH, y que no publique nada.

La lista de hosts permitidos viene activa: localhost pasa, y un Host ajeno recibe host not allowed, lo que protege contra DNS rebinding. El emparejamiento se activa en cuanto hay un dispositivo emparejado.

~/.config/collie/.env (permisos 600)
COLLIE_HOST=127.0.0.1
COLLIE_PORT=8787
COLLIE_MUX=herdr
COLLIE_SKIP_SERVE=1
Revisar y emparejar
systemctl --user restart collie
collie doctor        # bind 127.0.0.1 · front-door: SKIP_SERVE
collie pair          # código de un solo uso para el celular
collie devices list

6. El celular

En Termux, la terminal para Android, instalé openssh y cloudflared. El bloque de configuración hace todo: pasa por Access, usa la llave y abre el reenvío hacia Collie.

  1. ssh pc. La primera vez del día, cloudflared muestra una URL para iniciar sesión.
  2. Abro http://localhost:8787 en el navegador del teléfono.
  3. Solo la primera vez: ejecuto collie pair dentro de esa sesión SSH y escribo el código en la configuración de Collie.
Termux · ~/.ssh/config
Host pc
  HostName pc.tu-dominio.dev
  User tu-usuario
  ProxyCommand cloudflared access ssh --hostname %h
  LocalForward 8787 127.0.0.1:8787
  ServerAliveInterval 30

Parte 7 · Más cosas a considerar

Importante para no dejar cosas inconclusas

Un túnel no es un login

Antes de configurar Access, cualquier computador con cloudflared access ssh --hostname … llegaba al banner de OpenSSH de mi máquina. El túnel evita abrir puertos, pero no pregunta quién eres.

Regla: túnel y Access van siempre juntos.

Todo el mundo llega desde ::1

Por el túnel, sshd ve cada conexión como local. fail2ban no puede distinguir a un atacante de ti, y si bloqueara, bloquearía localhost.

Regla: la identidad y los límites van en el borde, no en el host.

El archivo estaba bien; el socket, no

Con activación de socket, ListenAddress solo se aplica con daemon-reload y reiniciando ssh.socket. Además, sudo sshd -t falla con "Missing privilege separation directory: /run/sshd" cuando sshd no está corriendo, y eso corta una cadena con && sin que lo notes.

Regla: comprueba con ss -tln, no leyendo la configuración.

Un guion de diferencia

La aplicación de Access protegía pc-ssh.tu-dominio.dev y el túnel publicaba pc.tu-dominio.dev. En el panel todo se veía configurado, pero nada estaba protegido.

Regla: curl -sI tiene que devolver 302 hacia cloudflareaccess.com.

Un notebook cambia de Wi-Fi

sshd, el correo local y un proxy de base de datos levantado con --address 0.0.0.0 escuchaban en todas las interfaces, con el firewall apagado. En cualquier red pública eran alcanzables sin pasar por Cloudflare.

Regla: loopback por defecto, UFW encendido y ss -tln de vez en cuando.

cert.pem no es solo un certificado

El archivo que deja cloudflared tunnel login contiene un token de API de la cuenta, con el que se pueden administrar túneles del dominio. El túnel no lo necesita para correr. Borrar el archivo no revoca el token.

Regla: sácalo del equipo y revoca el token si no lo vas a usar.

Parte 8 · Confiar, pero comprobar

Cómo comprobarlo

Esto es lo que quedó escuchando en el computador. Nada sale de loopback, salvo el DNS de las máquinas virtuales de libvirt, en una interfaz que está apagada.

ss -tln (resumido)
LISTEN  127.0.0.1:22       sshd (vía ssh.socket)
LISTEN  [::1]:22           sshd
LISTEN  127.0.0.1:8787     collie
LISTEN  127.0.0.1:20241    cloudflared (métricas)
LISTEN  127.0.0.1:25       postfix
LISTEN  127.0.0.1:631      cups
PruebaResultado esperado
curl -sI https://pc.tu-dominio.dev302 hacia el login de Access
ssh por el túnel, sin sesiónPide login en el navegador; no aparece el banner de OpenSSH
ssh sin llaveSolo ofrece publickey
sudo ufw status verbosedeny (incoming), sin reglas abiertas
collie doctorbind 127.0.0.1 y emparejamiento activo
curl -H 'Host: otro.com' 127.0.0.1:8787/api/confighost not allowed
La misma llamada, sin tokendevice not paired

Parte 9 · Sin vender humo

Límites y pendientes

Lo que esto no resuelve

  • Un celular desbloqueado, con la sesión de Access vigente, equivale a una terminal abierta. La documentación de Collie lo dice así.
  • Collie corre con mis permisos: sudo, ~/.ssh, mis repositorios.
  • Un dispositivo emparejado puede leer los archivos del workspace, incluidos los .env. Abro workspaces por repositorio, nunca en ~.

Pendientes al escribir esto

  • Passphrase en la llave del celular, para que robar el teléfono no baste.
  • PermitOpen 127.0.0.1:8787, para que SSH solo pueda reenviar hacia Collie.
  • Vencimiento del emparejamiento: collie devices set-expiry <dispositivo> 30d.
  • Access con sesión más corta y, si no viajo, restringido a mi país.

Si pierdo el celular

  1. collie devices revoke <dispositivo>
  2. Quitar su llave de ~/.ssh/authorized_keys.
  3. Revocar la sesión en Zero Trust → Usuarios.

Cada paso cierra una barrera distinta, así que conviene hacer los tres.

Parte 10 · Para seguir leyendo

Referencias

Agentes y terminal

Acceso remoto

Sistema y red