🛡️ sandbox-labs GitHub ↗

02 · Código generado por IA#

En una frase, para cualquiera: un modelo de lenguaje te escribe un programa en dos segundos. Nadie lo ha leído. Este caso lo ejecuta en una habitación que se construye para esa única vez y se demuele al terminar.

Estado real: 🟡 building · Carpeta: cases/02-ai-code-runner/ · Puerto: 8802


Por qué se realiza este caso#

El código que escribe un modelo no es malicioso ni es fiable: es desconocido. Y es desconocido de una forma nueva: se produce más rápido de lo que nadie puede revisarlo, y llega con un aire de corrección que invita a ejecutarlo sin mirar.

Lo que puede salir mal no requiere mala intención:

Lo que hace el fragmentoConsecuencia sin aislamiento
Un bucle infinitoEl servicio deja de responder a todo el mundo
open("/etc/hosts", "w") porque el modelo «arregló» algoSe modifica el sistema
Reservar memoria en un bucleEl sistema mata procesos al azar para recuperarla
Una petición a internet «para comprobar algo»Datos que salen sin que nadie lo decidiera
Leer una variable de entorno con una claveLa clave acaba en la salida del programa

Y hay algo más sutil, que es lo que este caso enseña de verdad: el segundo fragmento no debería ver lo que dejó el primero. Si el sandbox se reutiliza, un fragmento puede dejar un fichero preparado para el siguiente, o simplemente llenar el disco y arruinar la ejecución de otra persona.

La idea que enseña, y que ningún otro caso enseña#

Lo efímero como control de seguridad. El aislamiento no es solo una pared: es que la habitación no existe antes de la ejecución y no existe después.

Casos de uso reales#

el resultado antes de aplicarla.

mismo enunciado.

Cómo funciona#

flowchart LR
  U["👤 Código del modelo"] --> S["🧭 Servicio :8802"]
  S --> J
  subgraph J["🔒 Sandbox efímero"]
    P["🐍 python3 -c<br/>sin red · disco temporal<br/>entorno vacío"]
  end
  J --> O["📤 stdout · stderr<br/>código de salida"]
  J --> M["📊 Métricas<br/>tiempo · memoria · procesos"]
  J -. "al terminar" .-> D["🗑️ Destrucción total"]

El flujo que exige el diseño objetivo#

sequenceDiagram
  participant U as Panel o API
  participant Q as Cola de trabajos
  participant S as Sandbox efímero
  participant E as Evidencia
  U->>Q: registrar trabajo (código + límites)
  Q->>S: crear jaula para ESTA ejecución
  S->>S: ejecutar con timeout, memoria, PIDs y CPU acotados
  S->>E: stdout, stderr, métricas, controles aplicados
  S->>S: destruir jaula y su sistema de ficheros
  E-->>U: resultado + acta firmada

Esquemas#

Entrada — POST /api/run#

{ "code": "print(sum(range(10)))" }
CampoTipoObligatorioLímite
codetextoTamaño máximo de cuerpo acotado; se rechaza con 413 si se pasa

Salida#

{
  "stdout": "45\n",
  "stderr": "",
  "exitCode": 0,
  "timedOut": false,
  "runtime": "bwrap",
  "controls": { "network": "none", "filesystem": "ephemeral", "environment": "cleared" }
}
CampoQué es
stdout / stderrLo que el fragmento escribió, truncado a un techo
exitCodeCómo terminó. null si se le cortó
timedOutSi se agotó el tiempo, que es un resultado válido y no un error
runtimeQué frontera se usó de verdad: bwrap, unshare o ninguna
controlsQué se aplicó realmente, no qué se pidió

También hay GET /api/containment, que devuelve qué controles puede aplicar este equipo antes de ejecutar nada.

Software necesario#

ComponenteVersiónPara qué¿Obligatorio?
Python3.11+El servicio y el lenguaje de los fragmentos
Rust1.75+sandboxctl y el compilador de políticas
bubblewrap0.6+La jaula por ejecuciónSí para aislamiento real
util-linux (prlimit)2.37+Límites de memoria y procesosRecomendado
systemd en modo usuario249+cgroups v2: memory.max, pids.max, cpu.maxSolo para límites de recursos reales
Linux o WSL2kernel 5.10+Namespaces sin privilegios

Instalación#

sudo apt install bubblewrap util-linux python3
cargo build --release
cargo run -p sandboxctl -- doctor

Si doctor dice que no hay cgroups disponibles, el caso sigue funcionando pero sin límite de memoria ni de CPU, y así lo declara en controls. Esa es la regla del proyecto: lo que se aplica y lo que se reporta tienen que coincidir.

Cómo se ejecuta#

cargo run -p sandboxctl -- service up ai-code-runner
cargo run -p sandboxctl -- service down ai-code-runner

Procesos que se crean#

sandboxctl service up ai-code-runner
  │
  ├─ systemd --user scope        ← cgroup con los límites del servicio
  │   └─ bwrap                   ← la jaula del servicio
  │       └─ python3 app.py      ← el servicio, escucha en socket Unix
  │           └─ (por petición) el fragmento, en su propia jaula efímera
  │
  └─ sandboxctl service forward  ← puente TCP :8802 ↔ socket Unix

El objetivo del rediseño es que la jaula efímera no cuelgue del servicio sino del supervisor, para que el servicio pueda caerse sin dejar ejecuciones vivas.

Tiempo de carga#

OperaciónCoste típico
service up hasta que /health responde0,5–2 s
Crear la jaula efímera de una ejecución5–15 ms
Envoltura en cgroup30–80 ms
Un fragmento corto de Python40–150 ms
Techo de tiempo por ejecuciónconfigurable por política

Estado real y qué falta#

Construido: el servicio, la ejecución con red ausente, entorno vacío, sistema de ficheros temporal, techo de tiempo y reporte honesto de qué controles se aplicaron de verdad.

Falta, y es un rediseño, no un retoque: hoy el fragmento se ejecuta dentro del servicio web persistente. Debe pasar a un trabajo registrado con un sandbox efímero por ejecución, con cola limitada, cancelación, protección contra abuso y reproducibilidad.

Falta también: soporte progresivo de más lenguajes —JavaScript, TypeScript compilado, Rust, Go, Java, WASI—, y la regla que los gobierna: no se habilita un lenguaje que no tenga aislamiento y límites equivalentes a los de Python.

Si algo falla#

SíntomaCausaCómo se soluciona
timedOut: trueSe agotó el tiempo. Un bucle infinito termina así1. Revisar el fragmento. 2. Subir el techo de tiempo en policies/ si el trabajo lo justifica. 3. Si es un fragmento que debe tardar, dividirlo
controls no incluye memory ni cpuNo hay cgroups en este equipoActivar systemd en WSL2 ([boot] + systemd=true en /etc/wsl.conf, después wsl --shutdown). Detalle en no hay límites de memoria, CPU o procesos
El fragmento no puede instalar dependenciasNo tiene red1. Preinstalar lo necesario en la imagen del sandbox. 2. Si de verdad hace falta red, network: allowlist con los destinos concretos: el proxy registra cada intento
El fragmento escribe un fichero y al volver no estáEl sistema de ficheros es efímero y se destruye con la ejecuciónDeclarar una carpeta de salida en la política y escribir ahí. Todo lo demás desaparece a propósito
cuerpo ausente o demasiado grande (413)El fragmento supera el techo de cuerpoSubir el límite en app.py, o enviar el código por fichero montado en vez de por cuerpo
Dos ejecuciones seguidas se estorbanHoy el fragmento corre dentro del servicio persistente: es la limitación conocida del casoMientras no esté el rediseño, ejecutar de uno en uno. El sandbox efímero por ejecución está en ROADMAP
GET /api/containment dice que faltan controlesEl equipo no puede aplicar lo que la política pideEjecutar cargo run -p sandboxctl -- doctor: dice cuál falta y por qué. Con política estricta, la ejecución no ocurre, y eso hay que arreglarlo en el equipo, no en la política

Los fallos que afectan a cualquier caso —no se puede crear el sandbox, no hay cgroups, un puerto ocupado, procesos huérfanos, la compilación en Windows— están resueltos uno a uno en Cuando algo falla.


Ver también: Catálogo completo · Estado del proyecto · Referencia de políticas