HN’de Yayınlandı: WebAssembly QuickJS Sandbox’ında JavaScript Çalıştırma
(github.com/sebastianwessel)@sebastianwessel/quickjs, QuickJS motorunu WebAssembly’ye derlenmiş bir sandbox içinde JavaScript ve TypeScript kodunu güvenle çalıştıran bir TypeScript paketidir- Güvenilmeyen kodu izole ederek çalıştırmaya odaklanır ve QuickJS WebAssembly sandbox’ı üzerinden hafif ve hızlı bir çalışma ortamı sunar
- Temel Node.js modülleri desteği, sanal dosya sistemi bağlama, özel Node modülleri bağlama,
fetchistemcisi sağlama, test runner ve Chai tabanlıexpectiçerir - Kullanım örneğinde
loadQuickJs()ile QuickJS wasm bir kez yüklendikten sonrarunSandboxed()veevalCode()ile kod çalıştırılır;allowFetch,allowFs,envgibi seçenekler aktarılır - TypeScript uygulamalarında harici JavaScript kodu çalıştırması gereken geliştiriciler, izolasyon, performans ve API sadeliğini birlikte dikkate alan bir çalışma ortamı olarak kullanabilir
QuickJS WebAssembly Sandbox Paketi
@sebastianwessel/quickjs, JavaScript ve TypeScript kodunu WebAssembly sandbox’ı içinde çalıştırmayı sağlayan bir TypeScript paketidir- QuickJS motorunu WebAssembly’ye derleyerek kullanır ve güvenilmeyen kodu izole bir ortamda çalıştırma amacına uygundur
- Paket, hafif ve hızlı QuickJS motorundan yararlanarak kod çalıştırma için sağlam bir ortam sunar
- Tam dokümantasyon, örnekler ve çevrimiçi playground sunar
Sunulan Özellikler
- Güvenli çalıştırma: Güvenilmeyen JavaScript ve TypeScript kodunu güvenli ve izole bir ortamda çalıştırır
- Temel Node.js modülleri: Yaygın kullanım senaryoları için temel standart Node.js modülleri desteği sağlar
- Dosya sistemi: Sanal dosya sistemi bağlanabilir
- Özel Node modülleri: Kullanıcı tanımlı Node modülleri bağlanabilir
- Ağ çağrıları: http(s) çağrıları yapabilmek için
fetchistemcisi sağlar - Test desteği: Test runner ve Chai tabanlı
expectiçerir - Performans ve entegrasyon: QuickJS motorunun hafifliğinden ve verimliliğinden yararlanır; mevcut TypeScript projelerine kolayca entegre edilebilir
- Sade API: Sandbox içinde JavaScript ve TypeScript kodunu çalıştırmak ve yönetmek için kullanıcı dostu bir API sağlar
Temel Kullanım Akışı
- Önce
@jitl/quickjs-ng-wasmfile-release-synciçindenvariantalınır;@sebastianwessel/quickjsiçindenloadQuickJsveSandboxOptionskullanılır loadQuickJs(variant), QuickJS wasm’ini yükleyip başlatır; bu işlem kaynak yoğun olduğu için mümkünse yalnızca bir kez yapılması önerilirloadQuickJs(),runSandboxeddöndürür ve bu daha sonra sandbox çalıştırmaları için kullanılırSandboxOptionsörneği şu seçenekleri içerirallowFetch: true: Kodafetchenjekte eder ve veri isteklerine izin verirallowFs: true: Sanal dosya sistemini bağlar venode:fsmodülünü sağlarenv: Sandbox kodunda kullanılacak ortam değişkeni değerlerini aktarır
Çalıştırma Örneğinde Mümkün Olan Davranışlar
- Sandbox içindeki kod,
pathiçindenjoinimport eder vejoin('src','dist')çağrısıyla host sistem günlüğünesrc/distçıktısını yazar env.MY_ENV_VARdeğerini okuyup host sistem günlüğüne"env var value"çıktısını yazarnew URL('https://example.com')ile URL oluşturur,fetch(url)çağırır ve ardındanf.text()sonucunu döndürür- Kod,
runSandboxed(async ({ evalCode }) => evalCode(code), options)biçiminde çalıştırılır - Sonuç örneği
{ ok: true, data: '<!doctype html>\n<html>\n[....]</html>\n' }biçimindedir
Lisans ve Uygun Kullanım Alanları
- Bu proje MIT License ile sunulur
- TypeScript uygulaması içinde JavaScript kodunu güvenle çalıştırmak isteyen geliştiriciler için uygundur
- QuickJS WebAssembly sandbox’ı üzerinden performans ve güvenliği birlikte sağlayan bir çalışma ortamı sunar
1 yorum
Hacker News yorumları
quickjs-emscripten çalışma zamanı kütüphanesinin yazarı olarak, quickjs-emscripten için yaptığınız ergonomik standart kütüphane tarzını beğendim
Tarayıcıda ya da bir bundler ile çalıştırmayı denediniz mi merak ediyorum. Varyant adını string olarak alıp dinamik biçimde
import(variantName)içine verme yöntemi, Webpack gibi araçlarla pek iyi uyum sağlamayabilirGüvenlik uyarısı: Bu kütüphane, konuk tarafındaki güvenilmeyen kodun, host
fetchfonksiyonuyla aynı çerezleri kullanarakfetchçağırmasına olanak tanıyor.fetchi etkinleştirip güvenilmeyen kod çalıştırmamalısınızSandbox içindeki kod, host bağlamında kimliği doğrulanmış keyfi HTTP API çağrıları yapabiliyorsa, bu bir “sandbox” değildir
quickjs-emscripten’in düşük seviyeli olup sihirli özelliklerden kaçınmasının nedeni, sunduğu API’nin güvenli olduğunu rahatlıkla söyleyebilmek. Sihirli serileştirme ya da kolay ağ/dosya sistemi erişimi gibi özellik isteklerini çoğunlukla geri çeviriyorum; çünkü bu tür kodlar güvenlik hatalarına çok açık alanlar
Güvenilmeyen kod çalıştırırken yalnızca sandbox’ın kendisini değil, sandbox’a açtığınız API için yazdığınız kodu da dikkatle denetlemelisiniz
Başka bir HN kullanıcısının Fetch çerezleriyle ilgili sorduğu yorumu görünce olası güvenlik sorununu fark ettim
Daha fazla okuma: Figma eklenti sandbox güvenliği yazıları https://www.figma.com/blog/how-we-built-the-figma-plugin-sys..., https://www.figma.com/blog/an-update-on-plugin-security/, Quickjs-emscripten README https://github.com/justjake/quickjs-emscripten
Hem sunucu tarafında hem de tarayıcı tarafında JavaScript’i sandbox’a almak için birçok yöntem var, ama DOM erişimini “sandbox’a almanın” bir yolu var mı bilmiyorum
Örneğin güvenilmeyen bir üçüncü tarafa yalnızca önceden belirlenmiş bir konumdaki DOM öğelerine erişim vermek gibi. Bildiğim kadarıyla buna izin veren tek teknoloji iframe; ne yazık ki ağır ve yavaş
Eklenti barındırabilen bir uygulama geliştiriyorum; eklentilere DOM erişimi verirsem kelimenin tam anlamıyla her şeyi yapabilir hale geliyorlar gibi görünüyor
O güvenli değerlendirme fonksiyonu epey etkileyici. Çekirdeği muhtemelen burası: https://github.com/Agoric/realms-shim/blob/v1.1.0/src/evalua...
Bu sorunu platform düzeyinde çözmeye yönelik izole edilmiş web component’ler fikri de var: https://github.com/WICG/webcomponents/issues/1002
jsmirrors’tan esinlenerek “dommirrors” gibi bir şeyi kendiniz denemek mümkün, ama büyük iş. jsmirrors’u olduğu gibi kullanıp istediğiniz şeye bir ölçüde ulaşan geçici çözümler de var, fakat kullanımı rahat değil
Ancak DOM erişimini aracılık ederek ya da simüle ederek vermek neredeyse kesinlikle gerçekten istediğiniz şey değil. Karşı tarafın gerçekten ne yapmasına izin vermek istediğinizi belirleyip yalnızca o davranışı mümkün kılan yetkiyi vermek daha doğru
Örneğin bir yere düğme ekletmek istiyorsanız, bir üst öğeyi ankraj noktası olarak verip
document.createElementile DOM node’u oluşturmalarını sağlamanız gerektiğini düşünebilirsiniz. Ama aslında istediğiniz şey onlaradocument.createElementerişimi vermek değil, add-button yetkisi vermek. Bu yüzdenaddButtonimplement etmelisinizDevamı: <https://news.ycombinator.com/item?id=30703531#30706060>
CSP’nin bu iş için olduğunu söyleyenleri dinlememek iyi olur. Değil. Daha doğrusu CSP, asıl hedeflediği kullanım için bile kötü tasarlanmış ve kullanıcıya düşmanca bir çöp; bugünkü gibi implemente edilmemeli ve genişletilmemeliydi diye düşünüyorum. Buna bel bağlamak tehlikeli
Üst sayfada DOM içeriğinin boyutunu ölçüp iframe boyutunu belirli sınırlar içinde ayarlayabilirsiniz; böylece uygulama arayüzüne daha doğal şekilde entegre edilebilir
Kullanıcılar arası görünürlük seviyesi ve güven düzeyine bağlı olarak iframe içindeki kimliğe bürünme/phishing ve clickjacking girişimlerine dikkat etmek gerekir. İdeal olarak frame’in web isteği yapması tamamen engellenmeli; bu, görüntü yüklemenin de olmayacağı anlamına gelir
Böylece örneğin kullanıcı sahte bir parola formuna parolasını girmesi için kandırılsa bile veriyi frame dışına sızdırmanın bir yolu olmaz
iframe’in isteyebileceği kaynak türlerini sınırlamanın başlıca yolu Content-Security-Policy’dir; bununla üçüncü taraf görselleri, script’leri vb. tamamen kapatabilirsiniz
https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameE...
https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
Diğer sandbox özniteliklerini de açmalı, web kamerası gibi gizlilik açısından hassas DOM API’lerine erişimi kapatmalısınız
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...
Böylece izin verilecek HTML öğelerini ve özniteliklerini whitelist ile sınırlayabilirsiniz. Native DOM event’lerini dinlemeye de izin vermek karmaşıklaşır, ama imkânsız değil
Event adı string’ini ve event’i alacak öğe id’sini alan bir API gibi bir şeye ihtiyaç olur; gerçek DOM’da ilgili event’i algılayıp aynı event’i kullanılan JavaScript sandbox’ının içine kopyalamak gerekir. O sandbox içinde de az önce bahsedilen API’ye erişim olmalı
Backend’de HTML’i dinamik olarak değiştirip id değeri hash dizisi olan bir
divetiketi eklemek. Ayrıca HTML’i değiştirip kendi domain’inizden üçüncü taraf kodu isteyen birscriptetiketi koymak ve takip için oscriptetiketinin data özniteliğine hash değerini eklemekArka uçta üçüncü taraf kodunu değiştirip
document.vewindow.geçen tüm yerleridocument.getElementById(hash_value).ile değiştirmek ve tüm sorgu seçicilerin#hash_valueile başlamasını sağlamakElementprototipindeki.parentNodeyerine özel bir özellik koyup, sağlanan kapsayıcının dışına çıkma girişimlerini denetleyip engellemek gerekirArdından HTML belgesini tarayıcıya gönderirsiniz. Üçüncü taraf kodu bozulursa sorun değil. Kısıtlamalar üçüncü taraflara bildirilmeli ve sunucuya göndermeden önce kendi kodlarını test etmeleri onların sorumluluğunda olmalı
Önemli olan tek şey, kodun dinamik olarak sağlanan kapsayıcının dışına çıkamamasını sağlamak. Güvenlik ihlallerini bulmak için bu bölüm düzenli olarak test edilmeli
Gerçekte çalışmayabilir ama denemesi eğlenceli olur gibi
Geçen hafta tesadüfen QuickJS’i denedim ve sonunda isolated-vm’de karar kıldım. İkisi de güvenlik sözleşmesini karşılıyordu, ancak kurulum, kapatma ve
evalçalıştırma ek yükü açısından isolated-vm çok daha performanslıydıİlginç bir yaklaşım. Worker ile izole eden ve JavaScript ortamı temizleme teknikleri kullanan başka bir JavaScript sandbox kütüphanesinin[1] yazarı olarak, JavaScript’i yorumlama yönteminin — yani JavaScript içinde JavaScript’in ya da bu örnekte olduğu gibi WASM içinde JavaScript’in — en yüksek izolasyon düzeyini sağladığını düşünüyorum
Ayrıca doğrudan host JavaScript sanal makinesinin kendi hatalarına maruz kalmaz. Node’u hedefliyorsanız bu daha da önemli olabilir; çünkü bazı yeni akımlar hariç Node.js, Deno’nun aksine izolasyon ve sandboxing düşünülerek tasarlanmış gibi görünmüyor
API’ye bakınca
createRuntime’ınfetchdışında host ortam çağrıları tanımlamaya izin verip vermediği görünmüyor. Bu özellik epey faydalı olurdu; çünkü dış dünyayla iletişimi ya tamamen açmak ya da tamamen kapatmak yerine kontrollü biçimde sınırlamayı mümkün kılarBenzer şekilde tarayıcıları da desteklemiyor gibi görünüyor. En azından esm.sh ile hızlıca kontrol ettiğimde durum buydu. Bu da yararlı bir özellik olabilir
Ek yükün ne düzeyde olduğunu merak ettiğim için test edeceğim; yukarıda söylediğim gibi yaklaşımın kendisi oldukça sağlam görünüyor
[1] @exact-realty/lot
Host fonksiyonlarını dışa açma, guest fonksiyonlarını çağırma, özel modül yükleyiciler vb. için API’leri de var: https://github.com/justjake/quickjs-emscripten?tab=readme-ov...
newFunctionAPI dokümantasyonu: https://github.com/justjake/quickjs-emscripten/blob/main/doc...CPU’lar çok hızlandığına göre artık yorumlayıcı içinde yorumlayıcı çalıştıralım deniyor gibi görünüyor
QuickJS’te JavaScript çalıştırmanın avantajı olarak performansı göstermezdim. QuickJS, host JavaScript sanal makinesiyle hiç rekabet edemez
Ancak eski C yorumlayıcılarından veya JavaScript ile yazılmış yorumlayıcılardan daha hızlı olabilir
Bununla kullanıcı tarafından sağlanan JavaScript kodunu çalıştırmak mümkün olabilir. Kullanıcının TypeScript kodunu sandbox ortamında bundle etmenin bir yolunu arıyordum
QuickJS içinde webpack gibi bir bundler çalıştırmanın yolu konusunda önerisi olan var mı merak ediyorum
[1]: https://webcontainers.io/
Çok havalı. WASM’e derlendiyse tarayıcıda çalıştırmanın mümkün olup olmadığını merak ediyorum. Mümkünse ve
fetchisteklerini isteklere cookie eklemeden yapabiliyorsa ilginç olurduÖnceki işimde Quickjs-emscripten’de çok fazla segmentation fault ve sessiz hata gördüğümüz için proje askıya alınmıştı
Yeniden yapacak olsam, daha fazla programda düzgün çalışan ve resmen onaylanmış bir WASM bundle’ı olan bir motor kullanırdım
Kodun iframe ile güvenli biçimde sandbox’a alınabileceğini düşünüyordum ama emin değilim. Elbette kendi yorumlayıcınızı kullanırsanız daha sıkı zaman limitleri, özel API’ler gibi daha fazla özellik ekleyebilirsiniz
Şu anda kodu window message ile alan bir iframe kullanıyorum; giriş kodunu değerlendirip yanıtı window message ile geri gönderebildiği için oldukça iyi çalışıyor. Ancak sandbox dışına kaçış için bir açık olup olmadığından emin değilim
Daha karmaşık durumlarda, örneğin paket bağımlılıklarını Babel ile parse edip sonra CDN (esm.sh) kullanan bir import map oluşturmayı denedim, fakat CDN’lenmiş sürümün iyi çalışmadığı durumlar oldu
Bu yüzden StackBlitz kullandım ve oldukça iyi çalışıyor, ancak güvenli bağlam olmayan ortamlarda bazı sorunlar var
Sonunda kodu ve bağımlılıkları (package.json) alıp Docker container içinde Vite build’i yapan ve sonucu döndüren küçük bir web sunucusu yaptım. Fena çalışmıyor ama bazen yavaş
Tamamen istemci tarafında build edebilmek güzel olurdu; StackBlitz aşağı yukarı bunu yapıyor