Lilith.
⌕
Ilustración editorial: webframe ejecuta una web como un programa en medio segundo. Solo el aislamiento que lo rodea lo convierte en sandbox
Ilustración de Lilith · remezcla editorial

Simon Willison ha publicado una evaluación de webframe, una herramienta de Jeff Lindsay que ejecuta una página web como un programa de terminal. La evaluación la escribió Claude Opus 5.5 a partir de un prompt de Willison, y la conclusión tiene dos caras: para scraping la herramienta basta, pero el código ajeno necesita un aislamiento firme desde fuera.

Un programa de 5,3 MB ejecuta la página en el navegador que ya trae el sistema

Se le pasa a webframe una URL o un archivo y carga la página en un webview oculto. La salida de console.log va a la salida estándar y las excepciones, a la de errores. Con la opción -c evalúa en la página cada línea de la entrada estándar. Está escrito en Go sobre la biblioteca glaze, que usa el navegador del sistema: WKWebView en macOS, WebKitGTK en Linux y WebView2 en Windows. No incluye motor propio. Según el informe, toda la CLI ocupa unas 460 líneas de Go y JavaScript y se compila en un binario de 5,3 MB.

Willison le dio al modelo un único prompt: probar la herramienta, construir prototipos y evaluarla como sandbox y como sustituto del comando shot-scraper javascript. El repositorio deja claro que texto y código los escribió un modelo de lenguaje. Las pruebas se hicieron en Ubuntu 24.04 con WebKitGTK 2.52 y Xvfb. El arranque tarda unos 0,5 s. Salieron tres prototipos: un parche para webframe, un clon de shot-scraper llamado wfjs en unas 350 líneas de Go y una función run_js en Python que el modelo atacó con 20 fragmentos de código hostiles.

En scraping aguanta el ritmo de Playwright y a los agentes les da un motor JavaScript ligero

En scraping la comparación salió empatada. Obtener el título de example.com llevó de 1,08 a 1,17 s con wfjs y de 1,19 a 1,40 s con shot-scraper sobre Playwright. wfjs consume unos 640 MB de memoria contando WebKit y Xvfb, frente a unos 456 MB de Chrome más Python. Todos los ejemplos de la documentación de shot-scraper que no dependen de sus opciones exclusivas funcionaron sin cambios.

Para quien construye agentes, la segunda mitad es la más interesante. Un agente que necesite ejecutar JavaScript generado o descargado obtiene en webframe un motor de navegador real, con WASM, crypto.subtle y DOM, pero sin los objetos fs ni process. Playwright, en cambio, descarga su propio navegador. El método también merece atención. Willison encargó a un agente la evaluación de una nueva dependencia y recibió un parche, prototipos y una batería de ataques. Un informe así es un primer filtro barato que una herramienta debe superar antes de que un equipo empiece a fiarse de ella.

Por defecto, la página tiene red, localhost y memoria ilimitada

Tal como viene, webframe no es un sandbox por sí solo. La página tiene acceso completo a la red, localhost incluido, el almacenamiento sobrevive entre ejecuciones y el sandbox del renderizador de WebKit está desactivado. No hay límites de tiempo ni de memoria. Además, una página puede sobrescribir un gancho interno y responder a cada evaluación con un resultado falso. Para el scraping estorban otras tres cosas: las páginas cuya CSP bloquea eval, GitHub entre ellas, rechazan cualquier evaluación, el viewport mide 0×0 y una carga fallida puede quedarse colgada para siempre.

Ni siquiera el parche con bubblewrap cierra el hueco. El proceso de red de WebKit queda fuera, así que protege frente a un renderizador comprometido, no frente a una página que quiere salir a la red. Uno de los ataques lo demostró: una redirección con location.href hizo llegar una petición a localhost pese a una CSP estricta. Solo la detuvo un namespace de red vacío con unshare -rn. Un límite con RLIMIT_AS no sirve, porque WebKit reserva unos 75 GB de memoria virtual. Además, todas las cifras proceden de Linux y de tres mediciones; el informe no probó macOS ni Windows.

Lo decidirán un script world aislado y un límite de memoria con cgroup

La combinación que resistió todos los ataques probados tiene cinco capas: un namespace de red vacío, un perfil efímero con bubblewrap, un timeout que mata todo el grupo de procesos, un proceso nuevo por llamada y un resultado marcado con un nonce aleatorio. Cada llamada tarda así unos 450 ms. Para un uso serio, el informe pide además un límite de memoria y CPU con cgroup, un script world aislado que glaze todavía no ofrece y un worker de larga duración que elimine la espera del arranque.

La primera señal será si Lindsay incorpora a la rama principal el parche con perfil efímero y sandbox. La segunda, si glaze abre un mundo aislado para los scripts. Sin él, webframe seguirá siendo una herramienta práctica para hacer scraping de páginas de confianza y, para el código ajeno, solo una capa más.

El veredicto de Lilith

Bastó una línea con una redirección para que la página llamara a la puerta de localhost, aunque una CSP estricta le prohibía cualquier fetch. webframe es un motor ágil y rápido, pero quien ejecute en él JavaScript ajeno tiene que construirle la celda: una red vacía, un perfil desechable y un cronómetro que corte el proceso a tiempo.