Wie Caputchin Spiele sandboxt
Ein Caputchin-Spiel ist Drittanbieter-Code, der im Browser deines Besuchers läuft, auf deiner Seite. Ob es aus dem Marketplace kommt oder du es selbst hostest, Caputchin behandelt es als nicht-vertrauenswürdig by default und wickelt es in mehrere unabhängige Isolationsschichten, sodass ein fehlerhaftes oder feindseliges Spiel rendern, spielen und ein Ergebnis melden kann, aber nichts sonst erreichen kann: nicht deine Seite, nicht die Daten deines Besuchers, nicht das Netzwerk.
Diese Seite erklärt jede Maßnahme, warum jede existiert und wie das Ganze mit einer CSP interagiert, die du deiner eigenen Site hinzufügst.
Das Bedrohungsmodell in einer Zeile
Das Spiel führt beliebiges JavaScript (und möglicherweise WebAssembly) aus, das von einem Dritten geliefert wird. Das Isolationsziel ist daher: das Spiel kann rechnen und zeichnen, und es kann ein Ergebnis an das Widget zurückreichen, aber es kann nichts außerhalb seines eigenen Frames lesen oder beeinflussen. Jede Schicht unten dient diesem einen Ziel, und sie sind Defense in Depth, keine einzelne Schicht wird als perfekt vertraut.
Schicht 1: ein Opaque-Origin-gesandboxter Iframe
Jedes Spiel läuft in einem <iframe>, den das Widget mit einem bewusst minimalen sandbox-Attribut baut. Das einzige gewährte Token ist allow-scripts, weil das Spiel JavaScript ist und laufen muss. Alles andere wird vorenthalten, und die wichtigste Auslassung ist allow-same-origin.
Ohne allow-same-origin gibt der Browser dem Frame einen eindeutigen, Null-Origin. Diese eine Tatsache ist die tragende Grenze:
- Das Spiel kann das DOM, die Cookies,
localStorageoder die Caputchin-Session deiner Seite nicht lesen, es ist in einen Wegwerf-Origin versiegelt, der deinem fremd ist. - Weil der Frame auch nie dein Top-Window navigieren, Popups öffnen, Formulare absenden oder native Dialoge aufpoppen kann (all diese Fähigkeiten sind Sandbox-Tokens, die Caputchin nicht gewährt), gibt es keinen Pfad vom Spiel zurück hinaus zu deiner Seite.
Die Kombination aus allow-scripts ohne allow-same-origin ist der ganze Punkt: der Browser führt den Code des Spiels aus, behandelt den Frame aber als fremden Origin, der nichts von deinem berühren kann. Caputchin fügt einem Spiel-Frame nie allow-same-origin hinzu. (Als Folge kommt jede Nachricht, die das Spiel an das Widget postet, von Origin "null", was die Channel-Prüfungen des Widgets erwarten, ein Nicht-Null-Origin würde selbst eine fehlkonfigurierte Sandbox signalisieren.)
Schicht 2: das Dokument des Frames wird inline gebaut, nicht abgerufen
Das Widget richtet den Iframe nicht auf eine entfernte Seite. Es konstruiert das gesamte HTML-Dokument des Frames inline (per srcdoc) und reicht es dem Null-Origin-Frame. Dieses Dokument ist winzig: ein striktes CSP-Meta-Tag (nächste Schicht), ein kleiner Caputchin-Runtime-Bootstrap und ein <script>-Tag, das das Spiel-Bundle lädt.
Das ist derselbe Mechanismus für beide Auslieferungswege, nur die Bundle-URL unterscheidet sich:
| Spiel-Quelle | Bundle-URL im Inline-Dokument |
|---|---|
| Marketplace (plattform-aufgelöst) | Die gepinnte, integritäts-gehashte Bundle-URL, die Caputchin beim Mount aufgelöst hat. |
Selbst gehostet (dein game-src) | Die URL, die du geliefert hast. |
Weil das Dokument vom Widget verfasst wird, nicht vom Spiel, kontrolliert das Spiel nie die CSP, die Runtime oder die Struktur des Frames, nur was sein eigenes Bundle tut, sobald es geladen ist.
Schicht 3: Subresource Integrity für Marketplace-Spiele
Wenn Caputchin ein Marketplace-Spiel serviert, pinnt es das Bundle auf eine unveränderliche Version und zeichnet einen kryptografischen Hash (SHA-384) davon auf. Das <script>-Tag, das das Bundle lädt, trägt diesen Hash als integrity-Attribut mit crossorigin="anonymous", sodass der Browser selbst sich weigert, das Bundle auszuführen, wenn ein einziges Byte von dem abweicht, was gepinnt wurde. Ein kompromittiertes CDN kann keinen anderen Code unterschieben; der Load schlägt geschlossen fehl.
Ein selbst gehostetes Spiel hat keinen plattform-behaupteten Hash (du bist die Vertrauenswurzel für deine eigenen Bytes), also wird das integrity-Attribut für diesen Weg einfach weggelassen. Sieh dir den Replay-Vertrag dafür an, wie das Ergebnis eines Marketplace-Spiels unabhängig auf dem Server neu abgeleitet wird, eine separate Garantie zur Integrität.
Schicht 4: eine strikte Inline-Content-Security-Policy
Das Inline-Dokument trägt eine enge Content-Security-Policy. Obwohl der Frame schon Null-Origin und gesandboxt ist, schränkt die CSP weiter ein, was der geladene Code tun darf, sie verweigert alles standardmäßig und gewährt nur das Minimum neu:
| Direktive | Wert | Warum |
|---|---|---|
default-src | 'none' | Alles verweigern, was nicht unten ausdrücklich erlaubt ist. |
script-src | ein Hash von Caputchins eigener Inline-Runtime + der Origin des Spiel-Bundles + 'wasm-unsafe-eval' | Nur Caputchins genauen Bootstrap (per sha256--Hash gepinnt, nicht 'unsafe-inline') und das eigene Bundle des Spiels ausführen. 'wasm-unsafe-eval' lässt eine WASM-Engine kompilieren, ohne volles 'unsafe-eval' zu gewähren. |
connect-src | 'none' | Die Anti-Exfiltrations-Direktive. Das Spiel kann nirgendwo fetchen, XHRen, einen WebSocket öffnen oder beaconen. Kein Netzwerk-Egress überhaupt. |
img-src | data: (plus alle Skin-Asset-Origins, unten) | Sprites inline oder von einem erlaubten Asset-Host. |
media-src | data: (plus alle Skin-Asset-Origins, unten) | Audio/Video inline oder von einem erlaubten Asset-Host. |
font-src | data: | Nur Inline-Schriften. |
style-src | 'unsafe-inline' | Spiele setzen Inline-Styles; keine externen Stylesheets. (Styles können Daten nicht so exfiltrieren wie Skript oder Netzwerk.) |
Der Nettoeffekt: das Spiel läuft, rendert und meldet sein Ergebnis durch die SDK-Brücke und kann nichts sonst erreichen, besonders nicht das Netzwerk. Diese CSP wird von Caputchin gesetzt und ist nichts, das du konfigurierst; sie ist hier gelistet, damit du genau verstehst, wie eingeschränkt ein Spiel ist.
Die eine Stelle, an der eine Kundeneinstellung die Frame-CSP weitet
Skins können Bild- oder Audio-Felder auf eine absolute URL auf deinem eigenen CDN richten (siehe Skins). Damit diese Assets im abgeriegelten Frame laden, fügt Caputchin nur diese genauen Asset-Origins zur img-src / media-src des Frames hinzu, und nirgendwo sonst. script-src und connect-src werden nie geweitet, sodass selbst ein erlaubter Asset-Origin nicht genutzt werden kann, um Code zu laden oder einen Netzwerk-Kanal zu öffnen. Das ist der einzige, schmale Weg, auf dem ein konfigurierter Wert die Policy des Frames beeinflusst, und er ist immer noch nur Asset.
Schicht 5: ein Einweg-Ergebnis-Kanal
Das Spiel hat keine aufrufbare Fläche in deine Seite. Der einzige Weg hinaus ist die SDK-Brücke: das Spiel ruft eine einzelne Methode auf, die sein opakes Ergebnis (seinen Trace) per postMessage an das Widget sendet, das auf deinem Origin lebt. Das Widget ist es, das mit Caputchins API spricht; das Spiel tut es nie (es kann nicht, connect-src ist 'none'). Der Vertrauenspfad ist also Spiel → Widget → dein Backend, und jeder Sprung reicht nur je ein Ergebnis vorwärts, gewährt dem Spiel nie Reichweite rückwärts.
Die CSP, die du auf deiner eigenen Seite setzt
Die Schichten oben schützen dich und deinen Besucher vor dem Spiel. Eine Content-Security-Policy auf deiner eigenen Seite ist eine andere, ergänzende Kontrolle: sie schützt deine Seite vor allem, und Caputchin ist darauf ausgelegt, unter einer strikten zu laufen. Du musst deine CSP nicht lockern, um Caputchin einzubetten; du musst die wenigen Origins erlauben, die das Widget legitim nutzt.
Mindestens braucht das Widget:
| Direktive | Erlaube | Warum |
|---|---|---|
script-src | den Origin, von dem du das Widget-Skript lädst, plus 'unsafe-eval' | Das <script>, das <caputchin-widget> / <caputchin-game> definiert, das CDN, das du gewählt hast (jsDelivr, dein eigener Host oder caputchin.com). 'unsafe-eval' lässt die Instrumentierungs-Challenge laufen; es ist Pflicht, solange Instrumentierung an ist, und du kannst es weglassen, indem du Instrumentierung auf der Sicherheit-Seite des Keys ausschaltest. |
connect-src | https://caputchin.com | Das Widget ruft die Caputchin-API auf, um eine Verifizierung aufzusetzen und zu bestätigen. Richtest du das Widget auf einen anderen API-Host, erlaube den stattdessen. |
frame-src | https://caputchin.com | Regelt den Spiel-Iframe. Weil der Frame ein Inline-srcdoc-Dokument nutzt, wenden Browser deine frame-src darauf an, also erlaube den Caputchin-Origin hier. |
worker-src | blob: | Der Proof-of-Work-Löser läuft vollständig in Web Workern, die aus einer blob:-URL erstellt werden, ohne Main-Thread-Fallback, also ist das Pflicht. Lässt du worker-src weg, fallen Browser auf child-src dann default-src zurück, und default-src 'self' allein erlaubt blob: nicht. |
Füg optional 'wasm-unsafe-eval' zu script-src hinzu: es lässt den Proof-of-Work-Löser als schnelles WebAssembly laufen. Es ist nicht Pflicht, der Löser fällt ohne auf eine langsamere reine JavaScript-Implementierung zurück (immer noch im Worker).
Du musst den eigenen Bundle-Origin des Spiels (jsDelivr, das CDN eines Spiels, dein game-src-Host) in deiner Seiten-CSP nicht erlauben: dieses Bundle lädt im gesandboxten Frame, unter der eigenen CSP des Frames, die oben beschrieben ist, nicht unter der Policy deiner Seite. Deine Seite framet nur Caputchin; Caputchin schränkt ein, was darin läuft.
Wenn ein Caputchin-Element unter deiner CSP still nicht lädt, benennt deine Browser-Konsole die blockierte Direktive; füg den Origin oder das Keyword, das sie benennt, zu dieser Direktive hinzu. Fang strikt an und öffne genau das, worum die Konsole bittet. Du brauchst nie 'unsafe-inline'. Du brauchst blob: für Worker (worker-src) und, solange Instrumentierung an ist, 'unsafe-eval' in script-src; die 'unsafe-eval'-Anforderung geht weg, wenn du Instrumentierung auf der Sicherheit-Seite des Keys ausschaltest. 'wasm-unsafe-eval' ist optional (es beschleunigt den Proof-of-Work-Löser, der sonst langsamer in JavaScript im selben Worker läuft).
Warum so viele Schichten
Jede Schicht wäre für sich eine bedeutsame Grenze; zusammen bedeuten sie, dass ein Versagen in einer kein Durchbruch ist. Der Null-Origin allein mauert das Spiel schon von deinen Daten weg; die CSP allein tötet schon den Netzwerk-Egress; Integrität allein pinnt schon die genauen Bytes. Caputchin lässt sie alle laufen, weil nicht-vertrauenswürdiger Drittanbieter-Code genau der Ort ist, an dem Defense in Depth sich auszahlt.
Siehe auch
- Wie Spiele Bots widerstehen: die server-autoritative Seite der Spiel-Integrität.
- Der Replay-Vertrag: wie das Ergebnis eines Spiels unabhängig serverseitig neu abgeleitet wird.
- Ein selbst gehostetes Spiel bauen: die Bundle-Beschränkungen, die diese Sandboxes Autoren auferlegen.
- Das Widget aus einem CDN laden: den Skript-Origin wählen, den deine Seiten-CSP erlauben muss.