1 คะแนน โดย GN⁺ 2024-07-08 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • @sebastianwessel/quickjs คือแพ็กเกจ TypeScript สำหรับรันโค้ด JavaScript และ TypeScriptอย่างปลอดภัยภายใน sandbox ที่คอมไพล์เอนจิน QuickJS เป็น WebAssembly
  • มุ่งเน้นการแยกและรันโค้ดที่ไม่น่าเชื่อถือ โดยให้สภาพแวดล้อมการรันที่เบาและรวดเร็วผ่าน QuickJS WebAssembly sandbox
  • รองรับโมดูล Node.js พื้นฐาน, การเมานต์ระบบไฟล์เสมือน, การเมานต์โมดูล Node แบบกำหนดเอง, มี fetch client ให้ใช้งาน, รวมถึง test runner และ expect ที่อิงกับ Chai
  • ตัวอย่างการใช้งานคือโหลด QuickJS wasm หนึ่งครั้งด้วย loadQuickJs() แล้วรันโค้ดด้วย runSandboxed() และ evalCode() พร้อมส่งออปชันอย่าง allowFetch, allowFs, env
  • นักพัฒนาที่ต้องรันโค้ด JavaScript ภายนอกในแอปพลิเคชัน TypeScript สามารถใช้เป็นสภาพแวดล้อมรันที่คำนึงถึงทั้งการแยก, ประสิทธิภาพ และความเรียบง่ายของ API

แพ็กเกจ QuickJS WebAssembly sandbox

  • @sebastianwessel/quickjs คือแพ็กเกจ TypeScript ที่ทำให้สามารถรันโค้ด JavaScript และ TypeScriptภายใน WebAssembly sandbox ได้
  • ใช้เอนจิน QuickJS ที่คอมไพล์เป็น WebAssembly และออกแบบมาสำหรับการรันโค้ดที่ไม่น่าเชื่อถือในสภาพแวดล้อมที่แยกออกจากกัน
  • แพ็กเกจนี้ใช้ประโยชน์จากเอนจิน QuickJS ที่เบาและรวดเร็ว เพื่อมอบสภาพแวดล้อมที่มั่นคงสำหรับการรันโค้ด
  • มีเอกสารครบถ้วน, ตัวอย่าง, และออนไลน์ playground

ความสามารถที่มีให้

  • การรันอย่างปลอดภัย: รันโค้ด JavaScript และ TypeScript ที่ไม่น่าเชื่อถือได้อย่างปลอดภัยในสภาพแวดล้อมที่แยกออกจากกัน
  • โมดูล Node.js พื้นฐาน: รองรับโมดูลมาตรฐานพื้นฐานของ Node.js สำหรับกรณีใช้งานทั่วไป
  • ระบบไฟล์: สามารถเมานต์ระบบไฟล์เสมือนได้
  • โมดูล Node แบบกำหนดเอง: สามารถเมานต์โมดูล Node ที่ผู้ใช้กำหนดเองได้
  • การเรียกเครือข่าย: มี fetch client สำหรับเรียก http(s)
  • รองรับการทดสอบ: รวม test runner และ expect ที่อิงกับ Chai
  • ประสิทธิภาพและการผสานรวม: ใช้ความเบาและมีประสิทธิภาพของเอนจิน QuickJS และผสานเข้ากับโปรเจกต์ TypeScript เดิมได้ง่าย
  • API ที่เรียบง่าย: มี API ที่ใช้งานง่ายสำหรับรันและจัดการโค้ด JavaScript และ TypeScript ภายใน sandbox

ขั้นตอนการใช้งานพื้นฐาน

  • ก่อนอื่นให้นำเข้า variant จาก @jitl/quickjs-ng-wasmfile-release-sync และใช้ loadQuickJs กับ SandboxOptions จาก @sebastianwessel/quickjs
  • loadQuickJs(variant) จะโหลดและเริ่มต้น QuickJS wasm ซึ่งเป็นงานที่ใช้ทรัพยากรสูง จึงแนะนำให้ทำเพียงครั้งเดียวถ้าเป็นไปได้
  • loadQuickJs() จะคืนค่า runSandboxed เพื่อนำไปใช้รัน sandbox ต่อไป
  • ตัวอย่าง SandboxOptions มีออปชันดังนี้
    • allowFetch: true: inject fetch เข้าไปในโค้ดและอนุญาตให้ร้องขอข้อมูลได้
    • allowFs: true: เมานต์ระบบไฟล์เสมือนและให้โมดูล node:fs
    • env: ส่งค่าตัวแปรสภาพแวดล้อมให้โค้ดใน sandbox ใช้งาน

พฤติกรรมที่ทำได้ในตัวอย่างการรัน

  • โค้ดใน sandbox สามารถ import join จาก path และเรียก join('src','dist') เพื่อพิมพ์ src/dist ไปยังล็อกของระบบโฮสต์
  • สามารถอ่าน env.MY_ENV_VAR แล้วพิมพ์ "env var value" ไปยังล็อกของระบบโฮสต์
  • สามารถสร้าง URL ด้วย new URL('https://example.com') แล้วเรียก fetch(url) ก่อนคืนค่าผลลัพธ์ของ f.text()
  • รันโค้ดในรูปแบบ runSandboxed(async ({ evalCode }) => evalCode(code), options)
  • ตัวอย่างผลลัพธ์อยู่ในรูปแบบ { ok: true, data: '<!doctype html>\n<html>\n[....]</html>\n' }

ไลเซนส์และกรณีใช้งานที่เหมาะสม

  • โปรเจกต์นี้เผยแพร่ภายใต้ MIT License
  • เหมาะสำหรับนักพัฒนาที่ต้องการรันโค้ด JavaScript อย่างปลอดภัยภายในแอปพลิเคชัน TypeScript
  • มอบสภาพแวดล้อมการรันที่ได้ทั้งประสิทธิภาพและความปลอดภัยผ่าน QuickJS WebAssembly sandbox

1 ความคิดเห็น

 
GN⁺ 2024-07-08
ความคิดเห็นบน Hacker News
  • ในฐานะผู้เขียนไลบรารีรันไทม์ quickjs-emscripten ผมชอบแนวทางสไตล์ ไลบรารีมาตรฐาน ที่ออกแบบมาให้ใช้งานสะดวกสำหรับ quickjs-emscripten
    อยากรู้ว่าได้ลองรันในเบราว์เซอร์หรือ bundler แล้วหรือยัง วิธีรับชื่อ variant เป็นสตริงแล้วส่งต่อแบบไดนามิกไปยัง import(variantName) อาจเข้ากับเครื่องมืออย่าง Webpack ได้ไม่ดีนัก
    คำเตือนด้านความปลอดภัย: ไลบรารีนี้เปิดให้โค้ดที่ไม่น่าเชื่อถือของ guest สามารถเรียก fetch ด้วยคุกกี้เดียวกับฟังก์ชัน fetch ของ host ได้ ไม่ควรเปิด fetch แล้วรันโค้ดที่ไม่น่าเชื่อถือ
    ถ้าโค้ดใน sandbox สามารถเรียก HTTP API ใดๆ ที่ได้รับการยืนยันตัวตนในคอนเท็กซ์ของ host ได้ นั่นก็ไม่ใช่ “sandbox”
    เหตุผลที่ quickjs-emscripten อยู่ในระดับต่ำและหลีกเลี่ยงฟีเจอร์ที่เหมือนเวทมนตร์ ก็เพื่อให้พูดได้อย่างมั่นใจว่า API ที่ให้มานั้นปลอดภัย โดยทั่วไปจะปฏิเสธคำขอฟีเจอร์อย่างการ serialize แบบอัตโนมัติหรือการเข้าถึงเครือข่าย/ไฟล์ซิสเต็มที่ง่ายเกินไป เพราะโค้ดลักษณะนี้เป็นพื้นที่ที่เกิดความผิดพลาดด้านความปลอดภัยได้ง่าย
    เมื่อรันโค้ดที่ไม่น่าเชื่อถือ ต้องตรวจสอบอย่างรอบคอบไม่เพียงแค่ตัว sandbox เอง แต่รวมถึงโค้ดที่เขียนขึ้นสำหรับ API ที่เปิดให้ sandbox ใช้ด้วย
    ผมสังเกตเห็นประเด็นความปลอดภัยที่อาจเกิดขึ้นนี้หลังจากเห็นคอมเมนต์ที่ผู้ใช้ HN อีกคนถามเรื่องคุกกี้ของ Fetch
    อ่านเพิ่มเติม: บทความด้านความปลอดภัยของ sandbox สำหรับปลั๊กอิน 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

    • ถ้าใช้ iframe ก็อยากรู้ว่าจะป้องกัน ปัญหาคุกกี้ของ fetch ได้หรือไม่ ไม่ว่าจะใช้ไลบรารีนี้หรือไม่ก็ตาม หรือว่าแม้ใน iframe ก็ยังเป็นปัญหาอยู่
  • มีหลายวิธีในการแซนด์บ็อกซ์ JavaScript ทั้งฝั่งเซิร์ฟเวอร์และฝั่งเบราว์เซอร์ แต่ไม่แน่ใจว่ามีวิธี “แซนด์บ็อกซ์” การเข้าถึง DOM หรือไม่
    เช่น ให้บุคคลที่สามที่ไม่น่าเชื่อถือเข้าถึงได้เฉพาะองค์ประกอบ DOM ในตำแหน่งที่กำหนดไว้ล่วงหน้า เท่าที่รู้ เทคโนโลยีเดียวที่อนุญาตให้ทำแบบนี้ได้คือ iframe แต่น่าเสียดายที่มันหนักและช้า
    กำลังสร้างแอปที่โฮสต์ปลั๊กอินได้อยู่ แต่ถ้าให้ปลั๊กอินเข้าถึง DOM ได้ ก็ดูเหมือนว่าจะทำอะไรก็ได้จริง ๆ

    • Salesforce จัดการเรื่องนี้ด้วยการผสมผสานเว็บคอมโพเนนต์กับ ShadowRoot ที่แพตช์แล้ว ทำให้โค้ดที่มีการอ้างอิงถึง shadow root เดินออกไปยังส่วนที่เหลือของเอกสารไม่ได้ และจำกัดอ็อบเจ็กต์ global ที่สคริปต์ที่ไม่น่าเชื่อถือเข้าถึงได้ด้วย ฟังก์ชันประเมินผลที่ปลอดภัย ซึ่งเกี่ยวข้องกับ SES(Secure EcmaScript)
      ฟังก์ชันประเมินผลที่ปลอดภัยนั้นยอดเยี่ยมมาก แก่นหลักน่าจะอยู่ตรงนี้: https://github.com/Agoric/realms-shim/blob/v1.1.0/src/evalua...
      ยังมีไอเดีย เว็บคอมโพเนนต์แบบแยกกัก เพื่อแก้ปัญหานี้ในระดับแพลตฟอร์มด้วย: https://github.com/WICG/webcomponents/issues/1002
    • สิ่งที่ใกล้เคียงที่สุดคือโปรโตไทป์ jsmirrors ของ Allen Wirfs-Brock แต่สำหรับ DOM นั้นไม่ได้ไปถึงขั้นระบุเป็นสเปก และเท่าที่รู้ก็ไม่ได้ตั้งใจจะทำเช่นนั้นตั้งแต่แรกด้วย มันจัดการเฉพาะ capability สำหรับระบบการเขียนโปรแกรมที่ชื่อ JavaScript เท่านั้น
      อาจลองทำบางอย่างอย่าง “dommirrors” เองโดยได้แรงบันดาลใจจาก jsmirrors ได้ แต่นั่นเป็นงานใหญ่ มีวิธีอ้อม ๆ ที่ใช้ jsmirrors ตามเดิมเพื่อทำสิ่งที่ต้องการได้ในระดับหนึ่ง แต่ก็ไม่สะดวกต่อการใช้งาน
      อย่างไรก็ตาม การเป็นตัวกลางหรือจำลองการเข้าถึง DOM ให้แทบจะแน่นอนว่าไม่ใช่สิ่งที่ต้องการจริง ๆ ควรระบุให้ได้ว่าอยากอนุญาตให้อีกฝ่ายทำอะไรจริง ๆ แล้วให้เฉพาะ capability ที่ทำให้พฤติกรรมนั้นเป็นไปได้
      ตัวอย่างเช่น ถ้าต้องการให้เพิ่มปุ่มที่ไหนสักแห่ง อาจคิดว่าต้องให้จุดยึดเป็นองค์ประกอบ parent แล้วให้สร้างโหนด DOM ด้วย document.createElement แต่สิ่งที่ต้องการจริง ๆ ไม่ใช่ให้สิทธิ์เข้าถึง document.createElement แก่พวกเขา แต่คือ สิทธิ์ add-button ดังนั้นก็แค่ implement addButton ให้
      อ่านเพิ่มเติม: <https://news.ycombinator.com/item?id=30703531#30706060>
      ไม่ควรฟังคนที่บอกว่า CSP มีไว้สำหรับกรณีนี้ มันไม่ใช่ พูดให้ชัดคือ แม้แต่ในวัตถุประสงค์เดิมของ CSP เองก็ยังเป็นขยะที่ออกแบบแย่และเป็นปรปักษ์ต่อผู้ใช้ จึงคิดว่าไม่ควรถูก implement และขยายออกมาแบบที่เป็นอยู่ตอนนี้ การพึ่งพามันจึงอันตราย
    • หากต้องการเข้าถึงอย่างปลอดภัยจริง ๆ วิธีเดียวคือให้โค้ดของบุคคลที่สามอยู่ใน iframe ของโดเมนอื่น และตั้งค่า attribute sandbox
      ในหน้า parent สามารถวัดขนาดเนื้อหา DOM แล้วปรับขนาด iframe ภายในขอบเขตที่กำหนดได้ จึงผสานเข้ากับ UI ของแอปได้เป็นธรรมชาติมากขึ้น
      ต้องระวังความพยายามแอบอ้าง/ฟิชชิงและคลิกแจ็กกิงภายใน iframe ตามระดับการมองเห็นระหว่างผู้ใช้และระดับความไว้วางใจ ตามอุดมคติควรล็อกไม่ให้เฟรมทำเว็บรีเควสต์ได้เลย ซึ่งหมายความว่าโหลดรูปภาพก็ไม่ได้ด้วย
      ถ้าทำเช่นนั้น แม้ผู้ใช้จะถูกหลอกให้ป้อนรหัสผ่านลงในฟอร์มรหัสผ่านปลอม ก็จะไม่มีทางรั่วไหลข้อมูลออกนอกเฟรมได้
      วิธีหลักในการจำกัดประเภททรัพยากรที่ iframe ร้องขอได้คือ Content-Security-Policy ซึ่งใช้ปิดรูปภาพ สคริปต์ ฯลฯ จากบุคคลที่สามทั้งหมดได้
      https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameE...
      https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
      ควรเปิด attribute sandbox อื่น ๆ ด้วย และปิดการเข้าถึง DOM API ที่อ่อนไหวต่อความเป็นส่วนตัว เช่น เว็บแคม
      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...
    • วิธีที่เป็นไปได้คือน่าจะเพิ่ม API ให้บุคคลที่สามส่ง HTML ที่ต้องการเรนเดอร์มา ในรูปสตริง แล้วใช้ไลบรารีใดไลบรารีหนึ่งเพื่อพาร์ส จากนั้นสร้าง DOM ขึ้นใหม่โดยอิงจากข้อมูลที่พาร์สแล้ว
      แบบนี้จะจำกัดองค์ประกอบและแอตทริบิวต์ HTML ที่อนุญาตด้วย whitelist ได้ หากต้องการอนุญาตให้รับ native DOM event ด้วยก็จะซับซ้อนขึ้น แต่ไม่ใช่ว่าทำไม่ได้
      ต้องมี API ประมาณว่ารับสตริงชื่อ event และ id ขององค์ประกอบที่จะรับ event จากนั้นตรวจจับ event นั้นใน DOM จริง แล้วทำสำเนา event เดียวกันเข้าไปในแซนด์บ็อกซ์ JavaScript ที่ใช้อยู่ ภายในแซนด์บ็อกซ์นั้นต้องเข้าถึง API ที่กล่าวไปข้างต้นได้
    • ถ้าคิดแบบฉับพลัน อาจลองทำแบบนี้ได้: ให้แบ็กเอนด์ร้องขอโค้ดของบุคคลที่สาม แล้วผูกโค้ดนั้นเข้ากับลำดับ hash
      ที่แบ็กเอนด์ ให้แก้ HTML แบบไดนามิกเพื่อใส่แท็ก div ที่มีค่า id เป็นลำดับ hash เข้าไป อีกทั้งแก้ HTML เพื่อใส่แท็ก script ที่ร้องขอโค้ดของบุคคลที่สามจากโดเมนของตัวเอง และเพิ่มค่า hash ลงใน attribute data ของแท็ก script นั้นเพื่อใช้ติดตาม

แก้ไขโค้ดของบุคคลที่สามในแบ็กเอนด์ โดยแทนที่ทุกอินสแตนซ์ของ document. และ window. ด้วย document.getElementById(hash_value). และทำให้ตัวเลือกคิวรีทั้งหมดเริ่มต้นด้วย #hash_value
ต้องแทนที่ .parentNode ของโปรโตไทป์ Element ด้วยพร็อพเพอร์ตีแบบกำหนดเอง เพื่อตรวจสอบและบล็อกความพยายามที่จะออกนอกคอนเทนเนอร์ที่จัดเตรียมไว้
จากนั้นส่งเอกสาร HTML ไปยังเบราว์เซอร์ แม้โค้ดของบุคคลที่สามจะพังก็ไม่เป็นไร ต้องแจ้งข้อจำกัดให้บุคคลที่สามทราบ และพวกเขาต้องทดสอบโค้ดของตัวเองก่อนส่งมายังเซิร์ฟเวอร์
สิ่งสำคัญมีเพียงการทำให้โค้ดไม่สามารถออกนอกคอนเทนเนอร์ที่ให้มาแบบไดนามิกได้ ต้องทดสอบส่วนนี้เป็นประจำเพื่อหาการละเมิดด้านความปลอดภัย
ในทางปฏิบัติอาจไม่ทำงานก็ได้ แต่ดูน่าสนุกถ้าได้ลองทดลองดู

  • บังเอิญได้ลอง QuickJS เมื่อสัปดาห์ก่อน และสุดท้ายก็ลงเอยกับ isolated-vm ทั้งคู่ตอบโจทย์ด้านสัญญาความปลอดภัยได้ แต่ isolated-vm มีประสิทธิภาพดีกว่ามากในแง่โอเวอร์เฮดของการตั้งค่า การรื้อถอน และการรัน eval

  • เป็นแนวทางที่น่าสนใจ ในฐานะผู้เขียนไลบรารี JavaScript sandbox อีกตัว[1] ที่แยกด้วย worker และใช้เทคนิคการจัดระเบียบสภาพแวดล้อม JavaScript ผมมองว่าวิธีที่ตีความ JavaScript กล่าวคือ JavaScript ที่อยู่ใน JavaScript หรืออย่างกรณีนี้คือ JavaScript ที่อยู่ใน WASM ให้ระดับการแยกที่สูงที่สุด
    อีกทั้งยังไม่ต้องถูกเปิดรับบั๊กของ JavaScript virtual machine ฝั่งโฮสต์โดยตรงด้วย หากเป้าหมายคือ Node เรื่องนี้อาจสำคัญกว่า เพราะถ้าไม่นับแนวโน้มใหม่ ๆ บางส่วน Node.js ดูไม่ได้ถูกออกแบบโดยคำนึงถึง การแยกและการทำ sandboxing ต่างจาก Deno
    ดูจาก API แล้วไม่เห็นว่า createRuntime สามารถกำหนดการเรียกสภาพแวดล้อมโฮสต์นอกเหนือจาก fetch ได้หรือไม่ ฟีเจอร์นี้น่าจะมีประโยชน์มาก เพราะจะจำกัดการสื่อสารกับโลกภายนอกได้แบบควบคุม แทนที่จะเป็นการอนุญาตทั้งหมดหรือบล็อกทั้งหมด
    เช่นเดียวกัน ดูเหมือนว่าจะไม่รองรับเบราว์เซอร์ด้วย อย่างน้อยตอนลองตรวจคร่าว ๆ ผ่าน esm.sh ก็เป็นแบบนั้น ฟีเจอร์นี้ก็น่าจะมีประโยชน์เช่นกัน
    ผมสงสัยว่าโอเวอร์เฮดจะอยู่ระดับไหน เลยตั้งใจจะทดสอบดู และอย่างที่บอก แนวทางนี้ดูค่อนข้างแข็งแรงทีเดียว
    [1] @exact-realty/lot

  • ดูเหมือนจะเป็นแนวคิดว่า CPU เร็วขึ้นมากแล้ว งั้นตอนนี้ก็รัน interpreter ซ้อนใน interpreter กันเถอะ

  • ผมคงไม่ยก ประสิทธิภาพ เป็นข้อดีของการรัน JavaScript ใน QuickJS เพราะ QuickJS แข่งกับ JavaScript virtual machine ของโฮสต์ไม่ได้เลย
    แต่ก็อาจเร็วกว่าพวก C interpreter เก่า ๆ หรือ interpreter ที่เขียนด้วย JavaScript

    • ถ้าเวลาเริ่มโปรเซส Node.js เป็นปัจจัยหลักมากกว่าเวลารันสคริปต์ ก็อาจได้ประโยชน์ด้านประสิทธิภาพ น่าจะมีสคริปต์แบบ serverless function จำนวนไม่น้อยที่เข้าข่ายนี้
  • น่าจะใช้สิ่งนี้รันโค้ด JavaScript ที่ผู้ใช้ส่งมาได้ ผมกำลังหาวิธี bundle โค้ด TypeScript ของผู้ใช้ในสภาพแวดล้อม sandbox อยู่
    อยากรู้ว่ามีคำแนะนำไหมสำหรับวิธีรัน bundler อย่าง webpack ภายใน QuickJS

    • ไม่รู้วิธีทำด้วย QJS แต่ถ้าต้องการรัน bundler ในเบราว์เซอร์ WebContainers ดูเหมือนถูกสร้างมาเพื่อเรื่องนั้นพอดี
      [1]: https://webcontainers.io/
  • เจ๋งมาก ถ้าคอมไพล์เป็น WASM แล้วก็สงสัยว่าจะ รันในเบราว์เซอร์ ได้ไหม ถ้าได้ และสามารถส่งคำขอ fetch โดยไม่แนบคุกกี้ไปกับ request ได้ ก็น่าสนใจ

  • ที่ทำงานก่อนหน้านี้ โปรเจกต์ต้องถูกพักไว้เพราะเจอ segmentation fault และข้อผิดพลาดเงียบ ๆ ใน Quickjs-emscripten เยอะเกินไป
    ถ้าต้องทำใหม่ คงใช้เอนจินที่ทำงานได้ถูกต้องกับโปรแกรมมากกว่านี้ และมี WASM bundle ที่ได้รับการอนุมัติอย่างเป็นทางการ

  • เคยคิดว่าสามารถ sandbox โค้ดได้อย่างปลอดภัยด้วย iframe แต่ก็ไม่มั่นใจ แน่นอนว่าถ้าใช้ interpreter ของตัวเอง ก็น่าจะใส่ฟีเจอร์อย่าง time limit ที่ละเอียดกว่า และ custom API ได้มากขึ้น

    • ผมเองก็อยากรู้เรื่องนี้มากขึ้นเหมือนกัน โดยเฉพาะเมื่อเอา service worker มาผสมด้วย
      ตอนนี้ใช้ iframe ที่รับโค้ดผ่าน window message แล้วหลังจากประเมินโค้ดอินพุตก็ส่งคำตอบกลับทาง window message ได้ ซึ่งทำงานค่อนข้างดี แต่ก็ไม่มั่นใจว่าจะมีช่องให้หลุดออกจาก sandbox หรือไม่
      ในกรณีที่ซับซ้อนขึ้น เช่น dependency ของแพ็กเกจ ผมพยายามจะ parse ด้วย Babel แล้วสร้าง import map ที่ใช้ CDN (esm.sh) แต่บางครั้งเวอร์ชันที่อยู่บน CDN ก็ทำงานได้ไม่ดี
      เลยใช้ StackBlitz ซึ่งทำงานค่อนข้างดี แต่มีปัญหาบ้างในสภาพแวดล้อมที่ไม่ใช่ secure context
      สุดท้ายก็ทำเว็บเซิร์ฟเวอร์เล็ก ๆ ที่รับโค้ดและ dependency (package.json) แล้วรัน Vite build ใน Docker container จากนั้นส่งผลลัพธ์กลับมา มันทำงานได้พอใช้ แต่บางครั้งก็ช้า
      ถ้าบิลด์ได้ทั้งหมดฝั่ง client ก็คงดี และ StackBlitz ก็ทำงานประมาณนั้นอยู่
    • iframe มี สิทธิ์มากเกินไป ตัวอย่างเช่น iframe สามารถส่งข้อมูลรั่วไหลไปยัง third party ได้