1 poin oleh GN⁺ 2024-07-08 | 1 komentar | Bagikan ke WhatsApp
  • @sebastianwessel/quickjs adalah 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 dan expect berbasis Chai
  • Contoh penggunaan memuat QuickJS wasm sekali dengan loadQuickJs(), lalu menjalankan kode dengan runSandboxed() dan evalCode(), sambil meneruskan opsi seperti allowFetch, allowFs, dan env
  • 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/quickjs adalah 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 fetch untuk melakukan panggilan http(s)
  • Dukungan pengujian: Menyertakan test runner dan expect berbasis 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 variant dari @jitl/quickjs-ng-wasmfile-release-sync, lalu gunakan loadQuickJs dan SandboxOptions dari @sebastianwessel/quickjs
  • loadQuickJs(variant) memuat dan menginisialisasi QuickJS wasm, dan karena pekerjaan ini intensif sumber daya, disarankan dilakukan hanya sekali bila memungkinkan
  • loadQuickJs() mengembalikan runSandboxed, yang kemudian digunakan untuk menjalankan sandbox
  • Contoh SandboxOptions mencakup opsi berikut
    • allowFetch: true: Menyuntikkan fetch ke dalam kode dan mengizinkan permintaan data
    • allowFs: true: Me-mount sistem berkas virtual dan menyediakan modul node:fs
    • env: Meneruskan nilai variabel lingkungan untuk digunakan oleh kode sandbox

Perilaku yang dimungkinkan dalam contoh eksekusi

  • Kode di dalam sandbox dapat mengimpor join dari path dan memanggil join('src','dist'), lalu mencetak src/dist ke log sistem host
  • Membaca env.MY_ENV_VAR dan mencetak "env var value" ke log sistem host
  • Membuat URL dengan new URL('https://example.com'), memanggil fetch(url), lalu mengembalikan hasil f.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

 
GN⁺ 2024-07-08
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 Webpack
    Peringatan keamanan: Library ini memungkinkan kode tamu yang tidak tepercaya memanggil fetch dengan cookie yang sama seperti fungsi fetch milik host. Jangan mengaktifkan fetch lalu menjalankan kode yang tidak tepercaya
    Jika 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

    • Saya penasaran apakah dengan memakai iframe, masalah cookie fetch bisa dicegah terlepas dari apakah library ini digunakan atau tidak. Atau ingin tahu apakah masalahnya tetap ada bahkan di iframe
  • 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

    • Salesforce menangani ini dengan kombinasi web components dan ShadowRoot yang di-patch. Mereka mencegah kode yang memiliki referensi shadow root untuk keluar ke area lain dari dokumen, dan membatasi objek global yang bisa diakses skrip tidak tepercaya dengan fungsi evaluasi aman yang terkait dengan SES (Secure EcmaScript)
      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
    • Yang paling mendekati adalah prototipe jsmirrors dari Allen Wirfs-Brock, tetapi sejauh yang saya tahu, untuk DOM tidak pernah sampai dispesifikasikan dan sejak awal memang tidak terlalu dimaksudkan ke arah itu. Itu hanya menangani capability untuk sistem pemrograman bernama JavaScript
      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 ke document.createElement, melainkan capability add-button. Jadi implementasikan saja addButton
      Lihat 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
    • Untuk akses yang benar-benar aman, satu-satunya cara adalah memberi kode pihak ketiga iframe dari domain lain dan mengatur atribut sandbox
      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...
    • Cara yang mungkin adalah menambahkan API tempat pihak ketiga mengirim HTML yang ingin mereka render sebagai string, lalu mem-parsingnya dengan salah satu dari banyak library, kemudian membuat ulang DOM berdasarkan data hasil parsing
      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
    • Kalau dipikir spontan, mungkin bisa dilakukan seperti ini: backend meminta kode pihak ketiga dan mengaitkan kode itu dengan sebuah urutan hash
      Backend secara dinamis memodifikasi HTML untuk memasukkan tag div dengan nilai id berupa urutan hash. Backend juga memodifikasi HTML untuk memasukkan tag script yang meminta kode pihak ketiga dari domainnya sendiri, dan menambahkan nilai hash ke atribut data pada tag script itu untuk pelacakan

Di backend, ubah kode pihak ketiga sehingga semua instance document. dan window. diganti menjadi document.getElementById(hash_value)., dan semua selector kueri diawali dengan #hash_value
Properti .parentNode pada prototipe Element harus diganti dengan properti kustom, agar upaya keluar dari container yang diberikan bisa diperiksa dan diblokir
Lalu 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 eval

  • Pendekatan 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 createRuntime bisa mendefinisikan panggilan ke lingkungan host selain fetch. Fitur ini akan cukup berguna, karena komunikasi dengan dunia luar bisa dibatasi secara terkontrol, bukan sekadar membolehkan semuanya atau memblokir semuanya
    Demikian 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

  • 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

    • Jika waktu startup proses Node.js lebih dominan daripada waktu eksekusi skrip, mungkin ada keuntungan performa. Sepertinya cukup banyak skrip tipe serverless function termasuk dalam kategori ini
  • 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

    • Saya tidak tahu cara melakukannya dengan QJS, tetapi kalau ingin menjalankan bundler di browser, WebContainers tampaknya dibuat persis untuk itu
      [1]: https://webcontainers.io/
  • Sangat keren. Kalau sudah dikompilasi ke WASM, saya penasaran apakah bisa dijalankan di browser. Jika bisa, dan bisa melakukan permintaan fetch tanpa menyertakan cookie pada request, itu akan menarik

  • Di 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

    • Saya juga ingin tahu lebih banyak soal ini, terutama jika service worker ikut digabungkan
      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
    • iframe diberi izin yang berlebihan. Misalnya, iframe bisa mengeksfiltrasi data ke pihak ketiga