Cómo Caputchin pone los juegos en sandbox
Un juego de Caputchin es código de terceros que corre en el navegador de tu visitante, en tu página. Venga del marketplace o lo alojes tú mismo, Caputchin lo trata como no confiable por defecto y lo envuelve en varias capas de aislamiento independientes, así que un juego con bugs u hostil puede renderizar, jugar y reportar un resultado, pero no puede alcanzar nada más: ni tu página, ni los datos de tu visitante, ni la red.
Esta página explica cada medida, por qué existe cada una, y cómo todo el conjunto interactúa con una CSP que añades a tu propio sitio.
El modelo de amenazas en una línea
El juego corre JavaScript arbitrario (y posiblemente WebAssembly) suministrado por un tercero. El objetivo de aislamiento es por tanto: el juego puede computar y dibujar, y puede entregar un resultado de vuelta al widget, pero no puede leer ni afectar nada fuera de su propio marco. Cada capa de abajo sirve a ese único objetivo, y son defensa en profundidad, ninguna capa por sí sola se confía como perfecta.
Capa 1: un iframe en sandbox de origen opaco
Cada juego corre dentro de un <iframe> que el widget construye con un atributo sandbox deliberadamente mínimo. El único token concedido es allow-scripts, porque el juego es JavaScript y debe correr. Todo lo demás se retiene, y la omisión más importante es allow-same-origin.
Sin allow-same-origin el navegador le da al marco un origen nulo único. Ese único hecho es el límite que carga el peso:
- El juego no puede leer el DOM, las cookies, el
localStoragede tu página, ni la sesión de Caputchin, está sellado en un origen desechable que es ajeno al tuyo. - Como el marco tampoco puede nunca navegar tu ventana superior, abrir popups, enviar formularios, ni hacer aparecer diálogos nativos (todas esas capacidades son tokens de sandbox que Caputchin no concede), no hay camino del juego de vuelta a tu página.
La combinación de allow-scripts sin allow-same-origin es el punto entero: el navegador ejecuta el código del juego pero trata el marco como un origen ajeno que no puede tocar nada tuyo. Caputchin nunca añade allow-same-origin a un marco de juego. (Como consecuencia, cada mensaje que el juego postea al widget llega del origen "null", que las comprobaciones del canal del widget esperan, un origen no nulo señalaría por sí mismo un sandbox mal configurado.)
Capa 2: el documento del marco se construye inline, no se descarga
El widget no apunta el iframe a una página remota. Construye el documento HTML entero del marco inline (vía srcdoc) y se lo entrega al marco de origen nulo. Ese documento es diminuto: una meta tag de CSP estricta (siguiente capa), un pequeño bootstrap del runtime de Caputchin, y una tag <script> que carga el bundle del juego.
Este es el mismo mecanismo para ambos caminos de entrega, solo difiere la URL del bundle:
| Fuente del juego | URL del bundle en el documento inline |
|---|---|
| Marketplace (resuelto por la plataforma) | La URL del bundle, fijada y con hash de integridad, que Caputchin resolvió en el montaje. |
Autoalojado (tu game-src) | La URL que suministraste. |
Como el documento lo crea el widget en vez del juego, el juego nunca controla la CSP, el runtime, ni la estructura del marco, solo lo que su propio bundle hace una vez cargado.
Capa 3: Subresource Integrity para juegos del marketplace
Cuando Caputchin sirve un juego del marketplace fija el bundle a una versión inmutable y registra un hash criptográfico (SHA-384) de él. La tag <script> que carga el bundle lleva ese hash como un atributo integrity con crossorigin="anonymous", así que el propio navegador se niega a ejecutar el bundle si un solo byte difiere de lo que se fijó. Un CDN comprometido no puede sustituir código distinto; la carga falla cerrada.
Un juego autoalojado no tiene hash afirmado por la plataforma (tú eres la raíz de confianza de tus propios bytes), así que el atributo integrity simplemente se omite para ese camino. Mira el contrato de repetición para cómo el resultado de un juego del marketplace se vuelve a derivar de forma independiente en el servidor, una garantía separada de la integridad.
Capa 4: una Content-Security-Policy inline estricta
El documento inline lleva una Content-Security-Policy apretada. Aunque el marco ya es de origen nulo y está en sandbox, la CSP constriñe aún más lo que el código cargado puede hacer, niega todo por defecto y vuelve a conceder solo el mínimo:
| Directiva | Valor | Por qué |
|---|---|---|
default-src | 'none' | Niega todo lo no permitido explícitamente abajo. |
script-src | un hash del propio runtime inline de Caputchin + el origen del bundle del juego + 'wasm-unsafe-eval' | Corre solo el bootstrap exacto de Caputchin (fijado por un hash sha256-, no 'unsafe-inline') y el propio bundle del juego. 'wasm-unsafe-eval' deja que un motor WASM compile sin conceder el 'unsafe-eval' completo. |
connect-src | 'none' | La directiva anti-exfiltración. El juego no puede fetch, XHR, abrir un WebSocket, ni hacer beacon a ningún sitio. Sin salida de red en absoluto. |
img-src | data: (más cualquier origen de asset de skin, abajo) | Sprites inline, o de un host de asset permitido. |
media-src | data: (más cualquier origen de asset de skin, abajo) | Audio/vídeo inline, o de un host de asset permitido. |
font-src | data: | Solo fuentes inline. |
style-src | 'unsafe-inline' | Los juegos fijan estilos inline; sin hojas de estilo externas. (Los estilos no pueden exfiltrar datos como sí pueden el script o la red.) |
El efecto neto: el juego corre, renderiza, y reporta su resultado a través del puente del SDK, y no puede alcanzar nada más, especialmente no la red. Esta CSP la fija Caputchin y no es algo que tú configures; está listada aquí para que entiendas exactamente cuán confinado está un juego.
El único sitio donde un ajuste del cliente ensancha la CSP del marco
Los skins pueden apuntar campos de imagen o audio a una URL absoluta en tu propio CDN (mira skins). Para que esos assets carguen dentro del marco cerrado, Caputchin añade solo esos orígenes de asset exactos al img-src / media-src del marco, y en ningún otro sitio. script-src y connect-src nunca se ensanchan, así que ni siquiera un origen de asset permitido puede usarse para cargar código o abrir un canal de red. Esta es la única y estrecha forma en que un valor configurado afecta a la política del marco, y sigue siendo solo de assets.
Capa 5: un canal de resultado de una sola dirección
El juego no tiene superficie llamable hacia tu página. La única salida es el puente del SDK: el juego llama a un único método que hace postMessage de su resultado opaco (su traza) al widget, que vive en tu origen. El widget es el que habla con la API de Caputchin; el juego nunca lo hace (no puede, connect-src es 'none'). Así que el camino de confianza es juego → widget → tu backend, y cada salto solo pasa un resultado hacia adelante, nunca concede al juego alcance hacia atrás.
La CSP que fijas en tu propia página
Las capas de arriba te protegen a ti y a tu visitante del juego. Una Content-Security-Policy en tu propia página es un control distinto y complementario: protege tu página de todo, y Caputchin está diseñado para correr bajo una estricta. No tienes que relajar tu CSP para incrustar Caputchin; tienes que permitir los pocos orígenes que el widget usa legítimamente.
Como mínimo, el widget necesita:
| Directiva | Permitir | Por qué |
|---|---|---|
script-src | el origen del que cargas el script del widget, más 'unsafe-eval' | El <script> que define <caputchin-widget> / <caputchin-game>, el CDN que elegiste (jsDelivr, tu propio host, o caputchin.com). 'unsafe-eval' deja correr el reto de instrumentación; es obligatorio mientras la instrumentación está activada, y puedes quitarlo apagando la instrumentación en la página de Seguridad de la clave. |
connect-src | https://caputchin.com | El widget llama a la API de Caputchin para montar y confirmar una verificación. Si apuntas el widget a un host de API distinto, permite ese en su lugar. |
frame-src | https://caputchin.com | Gobierna el iframe del juego. Como el marco usa un documento srcdoc inline, los navegadores le aplican tu frame-src, así que permite el origen de Caputchin aquí. |
worker-src | blob: | El solver de proof-of-work corre enteramente en Web Workers creados desde una URL blob:, sin fallback en el hilo principal, así que esto es obligatorio. Si omites worker-src, los navegadores recaen en child-src y luego en default-src, y default-src 'self' por sí solo no permite blob:. |
Opcionalmente añade 'wasm-unsafe-eval' a script-src: deja correr el solver de proof-of-work como WebAssembly rápido. No es obligatorio, el solver recae en una implementación más lenta de JavaScript puro (todavía dentro del Worker) sin él.
No necesitas permitir el propio origen del bundle del juego (jsDelivr, el CDN de un juego, tu host game-src) en la CSP de tu página: ese bundle carga dentro del marco en sandbox, bajo la propia CSP del marco descrita arriba, no bajo la política de tu página. Tu página solo enmarca a Caputchin; Caputchin confina lo que corre dentro.
Si un elemento de Caputchin falla silenciosamente al cargar bajo tu CSP, la consola de tu navegador nombra la directiva bloqueada; añade el origen o la palabra clave que nombra a esa directiva. Empieza estricto y abre exactamente lo que la consola pide. Nunca necesitas 'unsafe-inline'. Sí necesitas blob: para los workers (worker-src) y, mientras la instrumentación está activada, 'unsafe-eval' en script-src; el requisito de 'unsafe-eval' desaparece si apagas la instrumentación en la página de Seguridad de la clave. 'wasm-unsafe-eval' es opcional (acelera el solver de proof-of-work, que de otro modo corre más lento en JavaScript dentro del mismo Worker).
Por qué tantas capas
Cada capa sería un límite significativo por sí sola; juntas significan que un fallo en una no es una brecha. El origen nulo por sí solo ya amuralla el juego lejos de tus datos; la CSP por sí sola ya mata la salida de red; la integridad por sí sola ya fija los bytes exactos. Caputchin corre todas ellas porque el código de terceros no confiable es exactamente el lugar donde la defensa en profundidad se gana el sueldo.
Véase también
- Cómo los juegos resisten a los bots: el lado autoritativo del servidor de la integridad del juego.
- El contrato de repetición: cómo el resultado de un juego se vuelve a derivar de forma independiente en el servidor.
- Construye un juego autoalojado: las restricciones de bundle que estos sandboxes imponen a los autores.
- Carga el widget desde un CDN: elegir el origen del script que la CSP de tu página debe permitir.