HN Show: Menjalankan JavaScript di sandbox WebAssembly QuickJS
(github.com/sebastianwessel)@sebastianwessel/quickjsadalah paket TypeScript untuk menjalankan kode JavaScript dan TypeScript dengan aman di dalam sandbox tempat mesin QuickJS dikompilasi ke WebAssembly- Fokusnya adalah mengisolasi dan menjalankan kode yang tidak tepercaya, serta menyediakan lingkungan eksekusi yang ringan dan cepat melalui sandbox WebAssembly QuickJS
- Mendukung modul Node.js dasar, mount sistem berkas virtual, mount modul Node kustom, menyediakan klien
fetch, serta menyertakan test runner danexpectberbasis Chai - Contoh penggunaan memuat QuickJS wasm sekali dengan
loadQuickJs(), lalu menjalankan kode denganrunSandboxed()danevalCode(), sambil meneruskan opsi sepertiallowFetch,allowFs, danenv - Pengembang yang perlu menjalankan kode JavaScript eksternal di aplikasi TypeScript dapat memanfaatkannya sebagai lingkungan eksekusi yang mempertimbangkan isolasi, performa, dan kesederhanaan API sekaligus
Paket sandbox WebAssembly QuickJS
@sebastianwessel/quickjsadalah paket TypeScript yang memungkinkan kode JavaScript dan TypeScript dijalankan di dalam sandbox WebAssembly- Paket ini menggunakan mesin QuickJS yang dikompilasi ke WebAssembly, dan dirancang untuk menjalankan kode yang tidak tepercaya di lingkungan terisolasi
- Paket ini memanfaatkan mesin QuickJS yang ringan dan cepat untuk menyediakan lingkungan yang tangguh bagi eksekusi kode
- Menyediakan dokumentasi lengkap, contoh, dan playground online
Fitur yang disediakan
- Eksekusi aman: Menjalankan kode JavaScript dan TypeScript yang tidak tepercaya secara aman dalam lingkungan terisolasi
- Modul Node.js dasar: Menyediakan dukungan untuk modul Node.js standar dasar untuk kasus penggunaan umum
- Sistem berkas: Dapat me-mount sistem berkas virtual
- Modul Node kustom: Dapat me-mount modul Node yang ditentukan pengguna
- Panggilan jaringan: Menyediakan klien
fetchuntuk melakukan panggilan http(s) - Dukungan pengujian: Menyertakan test runner dan
expectberbasis Chai - Performa dan integrasi: Memanfaatkan sifat ringan dan efisien dari mesin QuickJS serta mudah diintegrasikan ke proyek TypeScript yang sudah ada
- API sederhana: Menyediakan API yang ramah pengguna untuk menjalankan dan mengelola kode JavaScript dan TypeScript di dalam sandbox
Alur penggunaan dasar
- Pertama, impor
variantdari@jitl/quickjs-ng-wasmfile-release-sync, lalu gunakanloadQuickJsdanSandboxOptionsdari@sebastianwessel/quickjs loadQuickJs(variant)memuat dan menginisialisasi QuickJS wasm, dan karena pekerjaan ini intensif sumber daya, disarankan dilakukan hanya sekali bila memungkinkanloadQuickJs()mengembalikanrunSandboxed, yang kemudian digunakan untuk menjalankan sandbox- Contoh
SandboxOptionsmencakup opsi berikutallowFetch: true: Menyuntikkanfetchke dalam kode dan mengizinkan permintaan dataallowFs: true: Me-mount sistem berkas virtual dan menyediakan modulnode:fsenv: Meneruskan nilai variabel lingkungan untuk digunakan oleh kode sandbox
Perilaku yang dimungkinkan dalam contoh eksekusi
- Kode di dalam sandbox dapat mengimpor
joindaripathdan memanggiljoin('src','dist'), lalu mencetaksrc/distke log sistem host - Membaca
env.MY_ENV_VARdan mencetak"env var value"ke log sistem host - Membuat URL dengan
new URL('https://example.com'), memanggilfetch(url), lalu mengembalikan hasilf.text() - Menjalankan kode dalam bentuk
runSandboxed(async ({ evalCode }) => evalCode(code), options) - Contoh hasilnya berbentuk
{ ok: true, data: '<!doctype html>\n<html>\n[....]</html>\n' }
Lisensi dan penggunaan yang cocok
- Proyek ini tersedia di bawah MIT License
- Cocok untuk pengembang yang ingin menjalankan kode JavaScript dengan aman di dalam aplikasi TypeScript
- Menyediakan lingkungan eksekusi yang menjamin performa dan keamanan sekaligus melalui sandbox WebAssembly QuickJS
1 komentar
Opini Hacker News
Sebagai penulis library runtime quickjs-emscripten, saya menyukai gaya standard library yang ergonomis yang dibuat untuk quickjs-emscripten
Saya penasaran apakah sudah dicoba berjalan di browser atau bundler. Cara menerima nama varian sebagai string lalu meneruskannya secara dinamis ke
import(variantName)mungkin tidak cocok dengan tool seperti WebpackPeringatan keamanan: Library ini memungkinkan kode tamu yang tidak tepercaya memanggil
fetchdengan cookie yang sama seperti fungsifetchmilik host. Jangan mengaktifkanfetchlalu menjalankan kode yang tidak tepercayaJika kode di dalam sandbox dapat memanggil sembarang HTTP API yang terautentikasi sebagai konteks host, itu bukan “sandbox”
Alasan quickjs-emscripten bersifat low-level dan menghindari fitur yang terasa magis adalah agar kami bisa dengan yakin mengatakan bahwa API yang disediakannya aman. Kami umumnya menolak permintaan fitur seperti serialisasi magis atau akses jaringan/filesystem yang mudah, karena kode semacam ini adalah area yang rawan kesalahan keamanan
Saat menjalankan kode yang tidak tepercaya, Anda harus mengaudit dengan cermat bukan hanya sandbox itu sendiri, tetapi juga kode yang Anda tulis untuk API yang diekspos ke sandbox
Saya menyadari potensi isu keamanan ini setelah melihat komentar pengguna HN lain yang menanyakan soal cookie Fetch
Bacaan lebih lanjut: tulisan keamanan sandbox plugin Figma https://www.figma.com/blog/how-we-built-the-figma-plugin-sys..., https://www.figma.com/blog/an-update-on-plugin-security/, README Quickjs-emscripten https://github.com/justjake/quickjs-emscripten
Ada banyak cara untuk melakukan sandboxing JavaScript baik di sisi server maupun di sisi browser, tetapi saya tidak tahu apakah ada cara untuk “men-sandbox” akses DOM
Misalnya, memberi pihak ketiga yang tidak tepercaya akses hanya ke elemen DOM di lokasi yang sudah ditentukan. Sejauh yang saya tahu, satu-satunya teknologi yang memungkinkan ini adalah iframe, tetapi sayangnya berat dan lambat
Saya sedang membuat aplikasi yang bisa meng-host plugin, dan jika plugin diberi akses DOM, sepertinya mereka benar-benar bisa melakukan apa saja
Fungsi evaluasi aman itu cukup hebat. Intinya mungkin ada di sini: https://github.com/Agoric/realms-shim/blob/v1.1.0/src/evalua...
Ada juga gagasan web components terisolasi untuk memecahkan masalah ini di tingkat platform: https://github.com/WICG/webcomponents/issues/1002
Terinspirasi oleh jsmirrors, Anda bisa mencoba sendiri sesuatu seperti “dommirrors”, tetapi itu pekerjaan besar. Ada juga jalan memutar untuk mencapai sebagian dari yang Anda inginkan dengan memakai jsmirrors apa adanya, tetapi tidak nyaman digunakan
Namun, memediasi atau menyimulasikan akses DOM hampir pasti bukan hal yang sebenarnya Anda inginkan. Sebaiknya pahami dulu apa yang sebenarnya ingin Anda izinkan pihak lain lakukan, lalu berikan hanya capability yang memungkinkan tindakan itu
Misalnya, jika ingin mengizinkan mereka menambahkan tombol di suatu tempat, Anda mungkin berpikir perlu memberi titik jangkar berupa elemen induk dan membiarkan mereka membuat node DOM dengan
document.createElement. Tetapi yang sebenarnya Anda inginkan bukan memberi mereka akses kedocument.createElement, melainkan capability add-button. Jadi implementasikan sajaaddButtonLihat juga: <https://news.ycombinator.com/item?id=30703531#30706060>
Sebaiknya jangan dengarkan orang yang mengatakan bahwa CSP ditujukan untuk ini. Bukan. Tepatnya, menurut saya CSP bahkan untuk tujuan aslinya pun adalah sampah yang dirancang buruk dan bermusuhan terhadap pengguna, sehingga seharusnya tidak pernah diimplementasikan dan diperluas seperti sekarang. Berbahaya jika bergantung padanya
Dari halaman induk, Anda bisa mengukur ukuran konten DOM dan menyesuaikan ukuran iframe dalam batas tertentu, sehingga bisa lebih alami terintegrasi ke UI aplikasi
Bergantung pada tingkat paparan antar pengguna dan tingkat kepercayaan, Anda perlu berhati-hati terhadap upaya peniruan/phishing dan clickjacking di dalam iframe. Idealnya, frame harus dikunci agar tidak bisa melakukan request web sama sekali, yang berarti bahkan pemuatan gambar pun tidak bisa dilakukan
Dengan begitu, misalnya sekalipun pengguna tertipu memasukkan kata sandi ke formulir kata sandi palsu, tidak ada cara untuk membocorkan data keluar dari frame
Cara utama untuk membatasi jenis resource yang bisa diminta iframe adalah Content-Security-Policy, dan dengan ini Anda bisa mematikan semua gambar, skrip, dan sebagainya dari pihak ketiga
https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameE...
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Atribut sandbox lain juga perlu diaktifkan, dan akses ke API DOM yang sensitif terhadap privasi seperti webcam harus dimatikan
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/if...
https://developer.mozilla.org/en-US/docs/Web/Security/IFrame...
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Pe...
Dengan begitu, Anda bisa membatasi elemen dan atribut HTML yang diizinkan melalui whitelist. Jika ingin mengizinkan penerimaan event DOM native, itu menjadi rumit tetapi bukan mustahil
Anda memerlukan semacam API yang menerima string nama event dan id elemen yang akan menerima event, lalu mendeteksi event tersebut di DOM nyata dan mereplikasi event yang sama ke dalam sandbox JavaScript yang digunakan. Di dalam sandbox itu, API yang disebut tadi harus bisa diakses
Backend secara dinamis memodifikasi HTML untuk memasukkan tag
divdengan nilai id berupa urutan hash. Backend juga memodifikasi HTML untuk memasukkan tagscriptyang meminta kode pihak ketiga dari domainnya sendiri, dan menambahkan nilai hash ke atribut data pada tagscriptitu untuk pelacakanDi backend, ubah kode pihak ketiga sehingga semua instance
document.danwindow.diganti menjadidocument.getElementById(hash_value)., dan semua selector kueri diawali dengan#hash_valueProperti
.parentNodepada prototipeElementharus diganti dengan properti kustom, agar upaya keluar dari container yang diberikan bisa diperiksa dan diblokirLalu kirim dokumen HTML ke browser. Tidak masalah jika kode pihak ketiga rusak. Batasan tersebut harus diberitahukan kepada pihak ketiga, dan merekalah yang harus menguji kode mereka sendiri sebelum mengirimkannya ke server
Yang penting hanyalah memastikan kode tidak bisa keluar dari container yang diberikan secara dinamis. Bagian ini harus diuji secara berkala untuk menemukan pelanggaran keamanan
Mungkin saja ini tidak benar-benar berfungsi, tetapi sepertinya menarik untuk dicoba
Kebetulan minggu lalu saya mencoba QuickJS, dan akhirnya menetap pada isolated-vm. Keduanya memenuhi kontrak keamanan, tetapi isolated-vm jauh lebih baik performanya dalam hal overhead penyiapan, pembongkaran, dan eksekusi
evalPendekatan yang menarik. Sebagai penulis library sandbox JavaScript lain[1] yang melakukan isolasi dengan worker dan memakai teknik pembersihan lingkungan JavaScript, menurut saya cara menginterpretasikan JavaScript—yakni JavaScript di dalam JavaScript, atau seperti dalam kasus ini JavaScript di dalam WASM—memberikan tingkat isolasi tertinggi
Selain itu, kita tidak terekspos langsung pada bug di virtual machine JavaScript host itu sendiri. Ini bisa lebih penting jika menargetkan Node, karena selain beberapa arus terbaru, Node.js tampaknya tidak dirancang dengan mempertimbangkan isolasi dan sandboxing, berbeda dengan Deno
Melihat API-nya, saya tidak melihat apakah
createRuntimebisa mendefinisikan panggilan ke lingkungan host selainfetch. Fitur ini akan cukup berguna, karena komunikasi dengan dunia luar bisa dibatasi secara terkontrol, bukan sekadar membolehkan semuanya atau memblokir semuanyaDemikian pula, tampaknya browser juga tidak didukung. Setidaknya begitu saat saya mengeceknya sekilas dengan esm.sh. Ini juga bisa menjadi fitur yang berguna
Saya akan mengujinya karena penasaran seberapa besar overhead-nya, dan seperti yang saya katakan sebelumnya, pendekatannya sendiri terlihat cukup solid
[1] @exact-realty/lot
Ada juga API untuk mengekspos fungsi host, memanggil fungsi guest, custom module loader, dan lainnya: https://github.com/justjake/quickjs-emscripten?tab=readme-ov...
Dokumentasi API
newFunction: https://github.com/justjake/quickjs-emscripten/blob/main/doc...Sepertinya idenya sekarang adalah menjalankan interpreter di dalam interpreter, karena CPU sudah menjadi terlalu cepat
Saya tidak akan menyebut performa sebagai keunggulan menjalankan JavaScript di QuickJS. QuickJS sama sekali tidak bisa bersaing dengan virtual machine JavaScript host
Namun, bisa saja lebih cepat daripada interpreter C lama atau interpreter yang diimplementasikan dalam JavaScript
Dengan ini sepertinya bisa menjalankan kode JavaScript yang disediakan pengguna. Saya sedang mencari cara untuk membundel kode TypeScript pengguna dengan bundler di lingkungan sandbox
Saya penasaran apakah ada rekomendasi tentang cara menjalankan bundler seperti webpack di dalam QuickJS
[1]: https://webcontainers.io/
Sangat keren. Kalau sudah dikompilasi ke WASM, saya penasaran apakah bisa dijalankan di browser. Jika bisa, dan bisa melakukan permintaan
fetchtanpa menyertakan cookie pada request, itu akan menarikDi tempat kerja sebelumnya, proyek kami ditunda karena terlalu banyak segmentation fault dan error senyap di Quickjs-emscripten
Kalau harus mengulang, saya mungkin akan memakai engine yang bekerja dengan benar pada lebih banyak program dan memiliki bundel WASM yang disetujui secara resmi
Saya sempat berpikir kode bisa disandbox dengan aman menggunakan iframe, tetapi saya tidak yakin. Tentu saja, memakai interpreter sendiri tampaknya memungkinkan lebih banyak fitur seperti batas waktu yang lebih ketat dan API kustom
Saat ini saya memakai iframe yang menerima kode melalui window message, mengevaluasi kode input, lalu bisa mengirim respons kembali melalui window message, dan sejauh ini berjalan cukup baik. Namun saya tidak yakin apakah ada celah untuk keluar dari sandbox
Untuk kasus yang lebih kompleks, misalnya dependensi paket, saya sempat mencoba mem-parsingnya dengan Babel lalu membuat import map yang memakai CDN (esm.sh), tetapi versi yang sudah di-CDN-kan kadang tidak berjalan dengan baik
Jadi saya memakai StackBlitz, dan itu bekerja cukup baik, tetapi ada beberapa masalah di lingkungan yang bukan security context
Pada akhirnya saya membuat server web kecil yang menerima kode dan dependensi (package.json), menjalankan build Vite di dalam kontainer Docker, lalu mengembalikan hasilnya. Lumayan berjalan baik, tetapi kadang lambat
Akan bagus kalau build bisa dilakukan sepenuhnya di klien, dan StackBlitz kurang lebih melakukan hal semacam itu