La primera versión de nuestro sandbox IA tenía volúmenes de solo lectura, ejecución sin root, una lista de bloqueo de comandos que rechazaba npm install, y un timeout de 30 segundos. Era seguro. También era inútil.
El CEO miró la implementación y preguntó algo que cambió todo el diseño: "¿Por qué tantas restricciones? Los usuarios le pedirán a la IA que clone su repo, instale dependencias, ejecute su app, y encuentre errores. ¿Cómo se supone que haga eso si no puede instalar paquetes?"
Tenía razón. Habíamos optimizado para el modelo de amenaza equivocado.
La pregunta equivocada: "¿Cómo mantenemos segura a la IA?"
Cuando la mayoría de los ingenieros piensan en darle acceso shell a la IA, parten del miedo. ¿Y si ejecuta rm -rf /? ¿Y si instala malware? ¿Y si exfiltra datos?
Estas son preocupaciones válidas en un sistema compartido. No son preocupaciones válidas dentro de un contenedor desechable que existe únicamente para que la IA lo use.
La pregunta correcta: "¿Qué necesita la IA para realmente ayudar?"
Esto es lo que los desarrolladores le piden hacer a la IA cuando depuran un despliegue:
- "Clona mi repo y dime por qué falla el build"
- "Instala las dependencias y verifica si hay conflictos de versiones"
- "Ejecuta la app localmente y golpea el endpoint de salud"
- "Verifica si la base de datos es accesible desde la red de la app"
- "Lee la configuración de nginx y dime qué está mal"
Cada una de estas requiere instalación de paquetes, escritura de archivos, o piping de shell. Nuestra lista de bloqueo original -- que rechazaba apk, pip, npm, chmod, y pipes a sh -- hacía todas imposibles.
Lo que realmente construimos
El sandbox IA es un contenedor Alpine Linux completo que se ejecuta junto a tu aplicación:
┌─────────────────────────────┐
│ Host Docker │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Tu App │ │ Sandbox │ │
│ │ │ │ IA │ │
│ │ :3000 │ │ root │ │
│ │ │ │ 1GB RAM │ │
│ │ │ │ 2 CPUs │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ localhost │ │
│ └─────────────┘ │
│ red compartida │
│ volúmenes compartidos │
└─────────────────────────────┘Modo de red: container:{app_container_id} -- el sandbox comparte el espacio de nombres de red de la app. Puede alcanzar la app en localhost:3000. Puede alcanzar la base de datos de la app en db:5432. Misma vista de la red que la app misma.
Volúmenes: Escribibles. La IA puede leer tus archivos de configuración, modificarlos para probar correcciones, y verificar si el cambio funciona.
Usuario: Root. La IA puede apk add lo que necesite. ¿Proyecto Node? npm install. ¿Python? pip install. ¿Necesitas compilar algo? apk add build-base.
Herramientas pre-instaladas: curl, wget, dig, nc, jq, git, node, npm, python3, pip, bash.
Recursos: 1 GB RAM, 2 núcleos de CPU. Suficiente para npm install y builds pequeños.
Timeout: 5 minutos. Suficiente para git clone + npm install en un proyecto típico.
Lo que aún bloqueamos
La lista de bloqueo pasó de más de 30 patrones a 8:
rustconst BLOCKED_COMMANDS: &[&str] = &[
"rm -rf /",
"rm -rf /*",
"mkfs",
"shutdown",
"reboot",
"halt",
"poweroff",
"kill -9 1",
];Más fork bombs. Eso es todo.
Estos son comandos que no sirven propósito diagnóstico y destruirían el contenedor mismo. Todo lo demás -- operaciones de archivos, gestores de paquetes, piping de shell, herramientas de red, gestión de procesos -- está permitido.
El modelo de seguridad es el contenedor
Este es el insight que cambió el diseño. El sandbox ES el límite de seguridad. Es:
- Aislado: Un contenedor separado con su propio sistema de archivos
- Desechable: Destruido cuando la app se elimina, detenido cuando la app se detiene
- Con recursos limitados: 1 GB RAM, 2 núcleos de CPU, no puede consumir el host
- Con red limitada: Comparte la red de la app únicamente, no la red del host
- Efímero: Sin política de reinicio. Si el host reinicia, el sandbox desaparece
La pregunta no es "¿qué comandos debería permitirse ejecutar a la IA?" La pregunta es "¿cuál es el radio de explosión si la IA hace algo destructivo?" La respuesta: un contenedor desechable que se puede recrear en segundos.
La integración MCP
Cinco nuevas herramientas exponen el sandbox a través del servidor MCP de sh0:
| Herramienta | Riesgo | Propósito |
|---|---|---|
sandbox_exec_command | write | Ejecutar cualquier comando de shell |
sandbox_read_file | read | Leer archivos de los volúmenes de la app |
sandbox_list_processes | read | ps aux en el contenedor de la app |
sandbox_check_connectivity | read | Probar red con nc o curl |
sandbox_status | read | ¿Está el sandbox ejecutándose? |
La herramienta de riesgo de escritura sandbox_exec_command requiere una clave API con alcance write. Las claves de solo lectura pueden leer archivos y verificar conectividad pero no pueden ejecutar comandos arbitrarios. Este es el control de acceso real -- no una lista de bloqueo de comandos.
Los detalles de implementación que importan
Ciclo de vida idempotente. ensure_sandbox es el punto de entrada para cada llamada de herramienta. Si el sandbox existe y está ejecutándose, devuelve el ID. Si está detenido, lo reinicia. Si no existe, lo crea. Dos llamadas de herramientas concurrentes golpeando ensure_sandbox simultáneamente se manejan vía la respuesta 409 Conflict de Docker.
Creación no bloqueante. Cuando una app se despliega con sandbox_enabled: true, la creación del sandbox se ejecuta como tokio::spawn fire-and-forget. El pipeline de despliegue nunca espera por el sandbox. Si la creación del sandbox falla, registra una advertencia y el sandbox se crea perezosamente en la primera llamada de herramienta.
Ciclo de vida pareado. Detener la app, el sandbox se detiene. Iniciar la app, el sandbox se inicia. Eliminar la app, el sandbox se destruye. El sandbox sigue a la app.
Timeout doble. El comando se envuelve en la utilidad timeout de Alpine (kill del lado del servidor) Y tokio::time::timeout (guardia del lado del cliente). Si el timeout del servidor se activa, se detecta el código de salida 143 (SIGTERM) y se devuelve timed_out: true. Si de alguna manera eso falla, el timeout del cliente se activa 5 segundos después.
Lo que esto habilita
Con el sandbox, el asistente IA en sh0 ahora puede:
- Depuración profunda: Clonar el repo del usuario, instalar dependencias, buscar patrones de error, probar conectividad
- Análisis de configuración: Leer Dockerfile, nginx.conf, archivos de entorno, package.json -- entender la pila completa
- Pruebas en vivo: curl a los endpoints de la app desde la misma red, probar conexiones de base de datos, verificar DNS
- Auditoría de dependencias: Instalar el proyecto, verificar vulnerabilidades, validar compatibilidad de versiones
- Reproducción de builds: Clonar, instalar, construir -- reproducir la falla exacta que el usuario ve
Esta es la diferencia entre una IA que lee logs y una IA que realmente investiga.
La lección metodológica
Nuestro flujo de auditoría multi-sesión detectó la sobre-ingeniería inicial. Pero fue el CEO quien detectó el error de diseño fundamental -- porque piensa en lo que los usuarios necesitan, no en lo que los ingenieros temen.
El ciclo de construir-auditar-auditar-aprobar funciona mejor cuando el paso de "aprobar" incluye a alguien que pregunta "¿pero alguien realmente usará esto?" Un sandbox técnicamente perfecto que no puede instalar npm es un sandbox que nadie habilitará.
La seguridad es una restricción, no un objetivo. El objetivo es darle a la IA las herramientas para realmente ayudar. La restricción es hacerlo sin crear riesgo real. Un contenedor desechable con una lista de bloqueo mínima logra ambas cosas.