---
type: explanation
title: Cómo Caputchin pone los juegos en sandbox
summary: "Cada medida de aislamiento alrededor de un juego de Caputchin: el iframe de origen opaco, su único token de sandbox, la estricta Content-Security-Policy inline, el hashing de integridad, y cómo todo eso coexiste con una CSP que fijas en tu propia página."
---

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.

```mermaid
flowchart TD
  P["Tu página (tu origen)"] --> W["Widget de Caputchin (tu origen)"]
  W -->|construye el iframe| F["Iframe en sandbox (origen nulo)"]
  F --> G["El juego: JS / WASM"]
  G -->|solo resultado por postMessage| W
  subgraph Dentro del marco
    G
    L1["sandbox=allow-scripts solamente"]
    L2["Content-Security-Policy inline"]
    L3["Subresource Integrity (marketplace)"]
  end
```

## 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 `localStorage` de 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](/docs/marketplace-game-development/replay-contract) 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](/docs/game-customization/skin)). 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 [#the-csp-you-set-on-your-own-page]

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](/docs/integration-guides/cdn) que elegiste (jsDelivr, tu propio host, o `caputchin.com`). `'unsafe-eval'` deja correr el reto de [instrumentación](/docs/understanding-caputchin/instrumentation); es obligatorio mientras la instrumentación está activada, y puedes quitarlo apagando la instrumentación en la página de [Seguridad](/docs/site-keys/security) de la clave. |
| `connect-src` | `https://verify.caputchin.com` | El widget llama al servicio de verificación de Caputchin para montar y confirmar una verificación. Permite este origen exacto. 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](/docs/site-keys/security) 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](/docs/understanding-caputchin/gaming-anti-cheat): el lado autoritativo del servidor de la integridad del juego.
- [El contrato de repetición](/docs/marketplace-game-development/replay-contract): cómo el resultado de un juego se vuelve a derivar de forma independiente en el servidor.
- [Construye un juego autoalojado](/docs/custom-game-development/self-hosted-game): las restricciones de bundle que estos sandboxes imponen a los autores.
- [Carga el widget desde un CDN](/docs/integration-guides/cdn): elegir el origen del script que la CSP de tu página debe permitir.
