Caputchin
Memahami Caputchin

Bagaimana Caputchin men-sandbox game

Lihat sebagai Markdown

Sebuah game Caputchin adalah kode pihak-ketiga yang berjalan di peramban pengunjungmu, di halamanmu. Entah ia datang dari marketplace atau kamu hosting sendiri, Caputchin memperlakukannya sebagai tak dipercaya secara baku dan membungkusnya dalam beberapa lapis isolasi independen, jadi sebuah game yang buggy atau bermusuhan bisa merender, bermain, dan melaporkan hasil, tapi tak bisa menjangkau apa pun yang lain: bukan halamanmu, bukan data pengunjungmu, bukan jaringan.

Halaman ini menjelaskan setiap ukuran, mengapa masing-masing ada, dan bagaimana keseluruhannya berinteraksi dengan CSP yang kamu tambahkan ke situsmu sendiri.

Model ancaman dalam satu baris

Game menjalankan JavaScript sembarang (dan mungkin WebAssembly) yang dipasok pihak ketiga. Tujuan isolasinya karena itu: game bisa mengomputasi dan menggambar, dan ia bisa menyerahkan hasil kembali ke widget, tapi ia tak bisa membaca atau memengaruhi apa pun di luar frame-nya sendiri. Setiap lapis di bawah melayani satu tujuan itu, dan mereka adalah pertahanan berlapis, tak ada satu lapis pun yang dipercaya sempurna.

Lapis 1: iframe ber-sandbox origin-opaque

Setiap game berjalan di dalam sebuah <iframe> yang dibangun widget dengan atribut sandbox yang sengaja minimal. Token satu-satunya yang diberikan adalah allow-scripts, karena game adalah JavaScript dan harus berjalan. Semua yang lain ditahan, dan kelalaian terpenting adalah allow-same-origin.

Tanpa allow-same-origin peramban memberi frame sebuah origin null yang unik. Satu fakta itu adalah batas penyangga-beban:

  • Game tak bisa membaca DOM halamanmu, cookie, localStorage, atau sesi Caputchin, ia tersegel ke origin sekali-pakai yang asing bagimu.
  • Karena frame juga tak pernah bisa menavigasi jendela atasmu, membuka popup, mengirim formulir, atau memunculkan dialog native (semua kemampuan itu adalah token sandbox yang tak diberikan Caputchin), tak ada jalur dari game kembali keluar ke halamanmu.

Kombinasi allow-scripts tanpa allow-same-origin adalah seluruh intinya: peramban mengeksekusi kode game tapi memperlakukan frame sebagai origin asing yang tak bisa menyentuh apa pun milikmu. Caputchin tak pernah menambahkan allow-same-origin ke frame game. (Sebagai konsekuensi, setiap pesan yang diposting game ke widget tiba dari origin "null", yang diharapkan pemeriksaan kanal widget, origin non-null akan dengan sendirinya menandakan sandbox yang salah-konfigurasi.)

Lapis 2: dokumen frame dibangun inline, bukan diambil

Widget tak mengarahkan iframe ke halaman jarak-jauh. Ia membangun seluruh dokumen HTML frame inline (lewat srcdoc) dan menyerahkannya ke frame origin-null. Dokumen itu mungil: sebuah tag meta CSP ketat (lapis berikutnya), bootstrap runtime Caputchin kecil, dan satu tag <script> yang memuat bundel game.

Ini adalah mekanisme yang sama untuk kedua jalur pengiriman, hanya URL bundel yang berbeda:

Sumber gameURL bundel di dokumen inline
Marketplace (diselesaikan-platform)URL bundel ber-hash-integritas yang dipatok yang Caputchin selesaikan saat mount.
Swahosting (game-src-mu)URL yang kamu pasok.

Karena dokumen ditulis oleh widget ketimbang game, game tak pernah mengendalikan CSP, runtime, atau struktur frame, hanya apa yang bundel-nya sendiri lakukan setelah dimuat.

Lapis 3: Subresource Integrity untuk game marketplace

Saat Caputchin menyajikan game marketplace ia mematok bundel ke versi tak-berubah dan mencatat hash kriptografi (SHA-384)-nya. Tag <script> yang memuat bundel membawa hash itu sebagai atribut integrity dengan crossorigin="anonymous", jadi peramban itu sendiri menolak mengeksekusi bundel jika satu byte berbeda dari yang dipatok. CDN yang dikompromikan tak bisa menggantikan kode berbeda; muatan gagal tertutup.

Sebuah game swahosting tak punya hash yang ditegaskan-platform (kamu adalah akar kepercayaan untuk byte-mu sendiri), jadi atribut integrity sekadar dihilangkan untuk jalur itu. Lihat kontrak putar-ulang untuk bagaimana hasil sebuah game marketplace diturunkan-ulang secara independen di server, sebuah jaminan terpisah dari integritas.

Lapis 4: Content-Security-Policy inline yang ketat

Dokumen inline membawa Content-Security-Policy yang ketat. Meski frame sudah origin-null dan ber-sandbox, CSP lebih jauh membatasi apa yang boleh dilakukan kode yang dimuat, ia menolak semua secara baku dan memberi-ulang hanya minimum:

DirektifNilaiMengapa
default-src'none'Tolak semua yang tak diizinkan eksplisit di bawah.
script-srchash runtime inline Caputchin sendiri + origin bundel game + 'wasm-unsafe-eval'Jalankan hanya bootstrap persis Caputchin (dipatok oleh hash sha256-, bukan 'unsafe-inline') dan bundel game sendiri. 'wasm-unsafe-eval' membiarkan mesin WASM mengompilasi tanpa memberikan 'unsafe-eval' penuh.
connect-src'none'Direktif anti-eksfiltrasi. Game tak bisa fetch, XHR, membuka WebSocket, atau beacon ke mana pun. Tak ada egress jaringan sama sekali.
img-srcdata: (plus origin aset-skin mana pun, di bawah)Sprite inline, atau dari host aset yang diizinkan.
media-srcdata: (plus origin aset-skin mana pun, di bawah)Audio/video inline, atau dari host aset yang diizinkan.
font-srcdata:Hanya font inline.
style-src'unsafe-inline'Game menyetel gaya inline; tanpa stylesheet eksternal. (Gaya tak bisa mengeksfiltrasi data cara skrip atau jaringan bisa.)

Efek bersihnya: game berjalan, merender, dan melaporkan hasilnya lewat jembatan SDK, dan tak bisa menjangkau apa pun yang lain, khususnya bukan jaringan. CSP ini disetel Caputchin dan bukan sesuatu yang kamu konfigurasi; ia didaftarkan di sini agar kamu memahami persis betapa terkurungnya sebuah game.

Satu tempat di mana pengaturan pelanggan melebarkan CSP frame

Skin bisa mengarahkan bidang gambar atau audio ke URL absolut di CDN-mu sendiri (lihat skin). Agar aset itu memuat di dalam frame terkunci, Caputchin menambahkan hanya origin aset persis itu ke img-src / media-src frame, dan tak ke tempat lain. script-src dan connect-src tak pernah dilebarkan, jadi bahkan origin aset yang diizinkan tak bisa dipakai untuk memuat kode atau membuka kanal jaringan. Ini adalah satu-satunya cara sempit sebuah nilai terkonfigurasi memengaruhi kebijakan frame, dan ia masih hanya-aset.

Lapis 5: kanal hasil satu-arah

Game tak punya permukaan yang bisa dipanggil ke halamanmu. Satu-satunya jalan keluar adalah jembatan SDK: game memanggil satu metode yang postMessage-kan hasil opaque-nya (trace-nya) ke widget, yang hidup di origin-mu. Widget adalah yang berbicara ke API Caputchin; game tak pernah (ia tak bisa, connect-src adalah 'none'). Jadi jalur kepercayaan adalah game → widget → backend-mu, dan tiap lompatan hanya pernah meneruskan hasil ke depan, tak pernah memberi game jangkauan ke belakang.

CSP yang kamu setel di halamanmu sendiri

Lapis di atas melindungi kamu dan pengunjungmu dari game. Sebuah Content-Security-Policy di halamanmu sendiri adalah kontrol berbeda yang saling melengkapi: ia melindungi halamanmu dari segalanya.

Satu fakta menggerakkan semua isi bagian ini: frame game mewarisi kebijakan halamanmu. Frame itu dokumen srcdoc inline, dan peramban menerapkan CSP halaman penyemat pada dokumen semacam itu, di atas kebijakan frame sendiri. Jadi kebijakan terkunci frame yang ditunjukkan di atas adalah lantai yang ditambahkan Caputchin, bukan pengganti milikmu. Apa pun yang berjalan di dalam frame harus memenuhi keduanya, artinya kebijakan halamanmu harus mengizinkan bundel game dan bootstrap frame sendiri, walau halamanmu tak memuat keduanya secara langsung.

Minimal, widget butuh:

DirektifIzinkanMengapa
script-srcorigin tempat kamu memuat skrip widget, plus 'unsafe-eval'<script> yang mendefinisikan <caputchin-widget> / <caputchin-game>, CDN yang kamu pilih (jsDelivr, host-mu sendiri, atau caputchin.com). 'unsafe-eval' membiarkan tantangan instrumentasi berjalan; ia wajib selagi instrumentasi menyala, dan kamu bisa menghilangkannya dengan mematikan instrumentasi di halaman Keamanan key.
script-src (khusus game)origin bundel game, plus 'unsafe-inline' dan 'wasm-unsafe-eval'Karena frame mewarisi kebijakan ini. Origin bundel adalah https://games.caputchin.com untuk game marketplace, atau host / CDN-mu sendiri untuk game swahosting. 'unsafe-inline' menutupi skrip bootstrap frame sendiri, yang dipatok frame lewat hash sha256- tapi tak bisa dipatok halamanmu, karena hash itu berubah tiap rilis widget. 'wasm-unsafe-eval' dibutuhkan game yang dibangun di atas mesin WebAssembly. Tak satu pun berlaku kalau kamu cuma memakai <caputchin-widget> tanpa game.
connect-srchttps://verify.caputchin.com, plus origin tempat kamu memuat skrip widgetDua panggilan terpisah. Widget memanggil layanan verifikasi Caputchin untuk menyiapkan dan memastikan sebuah verifikasi, jadi izinkan origin yang persis itu (jika kamu mengarahkan widget ke host API berbeda, izinkan itu sebagai gantinya). Pemecah proof-of-work juga mengambil modul WebAssembly-nya dari sebelah skrip widget, jadi origin CDN itu tempatnya di sini sebagaimana di script-src. Menghosting widget di origin-mu sendiri menutupi yang kedua dengan 'self'.
worker-srcblob:Pemecah proof-of-work berjalan sepenuhnya di Web Worker yang dibuat dari URL blob:, tanpa cadangan thread-utama, jadi ini wajib. Jika kamu menghilangkan worker-src, peramban jatuh ke child-src lalu default-src, dan default-src 'self' sendiri tak mengizinkan blob:.

Tambahkan 'wasm-unsafe-eval' ke script-src supaya pemecah proof-of-work berjalan sebagai WebAssembly cepat. Baik kata kunci itu maupun origin tempat pemecah mengambil modul tak wajib agar verifikasi berhasil: kalau salah satunya hilang, pemecah jatuh ke implementasi JavaScript murni yang lebih lambat (masih di dalam Worker) dan verifikasi tetap berfungsi. Tapi keduanya baru berbuah kalau bersama. Pemecah mem-fetch modul WebAssembly itu, lalu mengompilasinya, jadi jalur cepat butuh origin skrip widget di connect-src dan 'wasm-unsafe-eval' di script-src; mengizinkan yang satu tanpa yang lain meninggalkanmu di pemecah lambat tanpa apa pun di konsol yang menjelaskan sebabnya.

Kamu tak perlu entri frame-src untuk game. Frame game adalah dokumen srcdoc inline, bukan dokumen yang diambil dari sebuah URL, jadi tak ada URL untuk dicocokkan frame-src, dan frame tetap memuat bahkan di bawah frame-src 'none'.

Yang memang kamu perlukan, dan yang mengejutkan orang, adalah origin bundel game di script-src halamanmu, walau halamanmu tak pernah memuat bundel itu: frame-lah yang memuatnya, dan frame berjalan di bawah kebijakanmu selain kebijakannya sendiri. Begitu juga untuk bootstrap frame dan untuk mesin game WebAssembly. Memberikannya tak melebarkan apa yang bisa dijangkau game: kebijakan frame sendiri tetap memaku connect-src 'none', frame tetap ber-origin opak, dan pemberian halamanmu tak bisa melonggarkan lantai yang dipasang frame pada dirinya sendiri.

'unsafe-inline' di script-src adalah bagian yang tak nyaman dari daftar itu, dan jujur saja begitu: hari ini ia wajib untuk menjalankan game, ia melemahkan perlindungan skrip inline halamanmu sendiri, dan alternatifnya (memaku hash sha256- bootstrap frame) tak praktis selagi hash itu bergerak tiap rilis widget. Kalau pertukaran itu tak bisa kamu terima untuk suatu halaman, pakai <caputchin-widget> di sana, yang tak butuh apa pun dari baris khusus game.

Jika sebuah elemen Caputchin diam-diam gagal memuat di bawah CSP-mu, konsol peramban menyebut direktif yang diblokir; tambahkan origin atau keyword yang ia sebut ke direktif itu. Mulai ketat dan buka persis apa yang diminta konsol, dan baca pelanggaran yang dilaporkan terhadap about:srcdoc secermat yang terhadap halamanmu sendiri: itu frame game yang membentur kebijakan warisanmu, dan itulah yang orang lewatkan. Kamu memang butuh blob: untuk worker (worker-src) dan, selagi instrumentasi menyala, 'unsafe-eval' di script-src; syarat 'unsafe-eval' lenyap jika kamu mematikan instrumentasi di halaman Keamanan key. 'unsafe-inline' cuma kamu butuhkan untuk menjalankan game, sesuai tabel di atas. Jalur cepat proof-of-work opsional tapi berpasangan: 'wasm-unsafe-eval' di script-src dan origin skrip widget di connect-src. Pasangan itulah satu-satunya kasus yang tak akan dibantu konsol, karena jatuh ke pemecah JavaScript bukan sebuah galat.

Kebijakan yang berhasil

Memuat widget dari jsDelivr, memakai game marketplace, dengan instrumentasi menyala:

default-src 'none';
script-src  https://cdn.jsdelivr.net https://games.caputchin.com 'unsafe-inline' 'unsafe-eval' 'wasm-unsafe-eval';
connect-src https://verify.caputchin.com https://cdn.jsdelivr.net;
worker-src  blob:;
img-src     'self' data: https:;
style-src   'unsafe-inline'

Buang https://games.caputchin.com, 'unsafe-inline' dan 'wasm-unsafe-eval' kalau kamu tak memakai game. Buang 'unsafe-eval' kalau kamu mematikan instrumentasi. Tukar https://cdn.jsdelivr.net dengan origin-mu sendiri di script-src maupun connect-src kalau kamu menghosting widget sendiri.

Mengapa begitu banyak lapis

Tiap lapis akan menjadi batas bermakna sendiri; bersama mereka berarti kegagalan di satu bukan pelanggaran. Origin null sendiri sudah menembok game dari datamu; CSP sendiri sudah mematikan egress jaringan; integritas sendiri sudah mematok byte persisnya. Caputchin menjalankan semuanya karena kode pihak-ketiga tak-dipercaya adalah persis tempat pertahanan berlapis membuktikan kegunaannya.

Lihat juga

Di halaman ini