ในที่สุด SharedArrayBuffer ก็กลับมาใช้งานได้ในเบราว์เซอร์อีกครั้ง แต่จะใช้งานได้ก็ต่อเมื่อหน้าเว็บอยู่ในสถานะ cross-origin isolated เท่านั้น ซึ่งเป็นสถานะที่ต้องใช้ response headers สองตัว การเพิ่ม header เหล่านี้ทำให้ผมต้องตัดสคริปต์จากบุคคลที่สาม (third-party script) ทั้งหมดออกจากไซต์ของผม ซึ่งเป็นการตัดสินใจที่เปลี่ยนรูปแบบการสร้าง front end ทั้งหมด รวมถึงข้อมูลที่อาจรั่วไหลออกไปด้วย
ทำไม header เหล่านี้ถึงสำคัญ
SharedArrayBuffer ช่วยให้ JavaScript สามารถทำ multithreading ได้อย่างแท้จริง ซึ่งเป็นเงื่อนไขจำเป็นสำหรับการรัน ffmpeg ภายในแท็บ เบราว์เซอร์สมัยใหม่ได้เปิดใช้งานฟีเจอร์นี้อีกครั้งหลังจากมีการใช้มาตรการป้องกันแบบ Spectre แต่พวกเขาได้ผูกมันไว้กับ cross-origin isolation เพื่อให้บรรลุการแยกส่วน (isolation) ดังกล่าว เซิร์ฟเวอร์ต้องส่ง:
Cross-Origin-Opener-Policy: same-originCross-Origin-Embedder-Policy: require-corp
Header ตัวที่สอง require-corp จะบอกเบราว์เซอร์ว่าทรัพยากรภายนอกใดๆ จะต้องมี header Cross-Origin-Resource-Policy หรือต้องถูกดึงข้อมูลผ่าน CORS (Cross-Origin Resource Sharing) บริการจากบุคคลที่สามส่วนใหญ่ไม่ได้ตั้งค่า header เหล่านี้ ดังนั้นสคริปต์, ฟอนต์ และ iframe ของพวกเขาจึงถูกบล็อกโดยสิ้นเชิง
สิ่งที่หายไปเมื่อเปิดใช้งานการแยกส่วน (isolation)
ทันทีที่ header เหล่านี้เริ่มใช้งาน ผลกระทบแบบโดมิโนก็ปรากฏขึ้น:
- Analytics – ผู้ให้บริการส่วนใหญ่โหลดโค้ดติดตามด้วย
<script>แบบธรรมดาซึ่งเป็นการส่งคำขอแบบ no-CORS หากไม่มี header CORP คำขอนั้นจะถูกปฏิเสธ ทำให้ตัวติดตามไม่ทำงาน - Google Fonts – stylesheet ถูกดึงมาจาก
fonts.googleapis.comโดยไม่มี CORS เบราว์เซอร์จึงทิ้งไฟล์นั้นไป ทำให้หน้าเว็บไม่มีรูปแบบตัวอักษร (typography) ที่กำหนดไว้ - Embedded media – YouTube iframes และสคริปต์ widget ขาด CORP จึงทำให้ไม่สามารถแสดงผลได้
- OAuth pop-ups – ความสัมพันธ์แบบ
window.openerถูกทำลายโดยนโยบาย same-origin ที่เข้มงวด ทำให้ขั้นตอนการเข้าสู่ระบบผ่าน popup แบบปกติใช้งานไม่ได้
สรุปสั้นๆ คือ ทรัพยากรใดก็ตามที่พึ่งพาดอมเมนของบุคคลที่สามจะหายไป เว้นแต่โดเมนนั้นจะยอมรับกฎเกณฑ์ของ header แบบใหม่
การสร้างใหม่โดยไม่ต้องผ่านตัวกลาง
เมื่อต้องเผชิญกับไซต์ที่พังทลาย ผมจึงเขียน stack ของ front-end ใหม่โดยเน้นไปที่การใช้ self-hosted assets:
- Fonts and images ถูกส่งมาจาก origin ของผมเอง ช่วยลดความจำเป็นในการใช้ stylesheet ภายนอก
- Data APIs ถูกสร้างขึ้นบน backend ส่วนตัวที่ผมควบคุม ดังนั้นทุกคำขอจะอยู่ภายในโดเมนของผมเท่านั้น
- Analytics เปลี่ยนมาใช้ Cloudflare Worker ขนาดเล็กที่รับเหตุการณ์
POSTและจัดเก็บไว้ใน private bucket ฝั่ง client-side มีเพียงโค้ด fetch ไม่กี่บรรทัดเท่านั้น - Error reporting and session replay เครื่องมืออย่าง Sentry ถูกตัดออกไปทั้งหมด โดยความผิดพลาด (crash) ใดๆ จะถูกบันทึกลงใน endpoint ของผมเอง
หากยังจำเป็นต้องใช้ทรัพยากรภายนอก ทางเลือกเดียวที่ทำได้คือการทำ proxy ผ่านเซิร์ฟเวอร์ที่คุณเป็นเจ้าของ โดยเพิ่ม header CORP ที่จำเป็นก่อนที่เบราว์เซอร์จะมองเห็น
ประโยชน์ด้านความเป็นส่วนตัว เทียบกับ ต้นทุนในการดำเนินงาน
ประโยชน์ที่เห็นได้ชัดทันทีคือ: ไซต์จะไม่รั่วไหลข้อมูลการใช้งานไปยังเครือข่ายโฆษณา, ผู้ให้บริการฟอนต์ หรือแพลตฟอร์มวิดีโออีกต่อไป ข้อมูล telemetry ทั้งหมดอยู่ภายใต้การควบคุมของผม และผมสามารถลบมันทิ้งเมื่อใดก็ได้ ความเป็นส่วนตัวระดับนี้ทำได้ยากหากใช้ stack ของบุคคลที่สามแบบทั่วไป
สิ่งที่ต้องแลกมาคือภาระในการดูแลรักษาที่เพิ่มขึ้น การโฮสต์ฟอนต์, การจัดการการจัดเก็บ analytics และการคอยอัปเดต proxy เป็นงานที่นักพัฒนาส่วนใหญ่มักจะจ้างบริการเฉพาะทางทำ นอกจากนี้ แนวทางนี้ยังหมายถึงการสูญเสียฟีเจอร์ที่บริการเหล่านั้นมีให้ เช่น การรวบรวมข้อผิดพลาดแบบเรียลไทม์ หรือการแสดงภาพ funnel แบบละเอียด
มุมมองแย้ง: ระบบนิเวศจะปรับตัวหรือไม่?
บางคนแย้งว่าในที่สุดผู้ให้บริการบุคคลที่สามจะเพิ่ม header ที่จำเป็น ทำให้การนำ cross-origin isolation มาใช้เป็นเรื่องง่าย มีบางรายที่ทำแล้ว แต่บริการส่วนใหญ่ที่ใช้กันอย่างแพร่หลายยังไม่ได้ทำ จนกว่าระบบนิเวศจะตามทัน นักพัฒนาต้องตัดสินใจว่าข้อดีด้านความเป็นส่วนตัวนั้นคุ้มค่ากับความพยายามทางวิศวกรรมที่ต้องใช้ในการทำ self-host หรือไม่
วิธีตรวจสอบว่าคุณได้รับการแยกส่วน (isolated) อย่างแท้จริง
ก่อนที่คุณจะเริ่มเขียนโค้ดใหม่ ให้ตรวจสอบว่าเบราว์เซอร์มองเห็นหน้าเว็บของคุณว่าถูกแยกส่วนแล้วหรือยัง:
crossOriginIsolated // should be true
typeof SharedArrayBuffer // should be "function"
หากการตรวจสอบใดการตรวจสอบหนึ่งล้มเหลว แสดงว่า header ไม่ได้ถูกนำมาใช้อย่างถูกต้อง และ SharedArrayBuffer จะยังคงใช้งานไม่ได้
ก้าวต่อไปสำหรับนักพัฒนาคืออะไร?
เมื่อเว็บแอปพลิเคชันจำนวนมากขึ้นต้องการประสิทธิภาพที่เพิ่มขึ้นจาก multithreaded JavaScript แรงกดดันต่อผู้ให้บริการบุคคลที่สามในการนำ CORP มาใช้จะเพิ่มมากขึ้น ในระหว่างนี้ โปรเจกต์ใดก็ตามที่ต้องการ SharedArrayBuffer ควรวางแผนกลยุทธ์การใช้ self-hosted asset หรือเลเยอร์ proxy ขนาดเล็ก นอกจากนี้ การคอยติดตามบันทึกการอัปเดต (release notes) ของเบราว์เซอร์เกี่ยวกับการเปลี่ยนแปลงข้อกำหนดการแยกส่วนก็เป็นสิ่งสำคัญเช่นกัน
สรุปสาระสำคัญ: การเปิดใช้งาน cross-origin isolation เพื่อปลดล็อก SharedArrayBuffer บีบให้ต้องตัดสินใจเลือกอย่างใดอย่างหนึ่ง ระหว่างการรักษาความสะดวกสบายจากการใช้ third-party scripts หรือการยอมแลกเพื่อเปลี่ยนไปใช้โมเดลความเป็นส่วนตัวที่รัดกุมและควบคุมได้ด้วยตนเองมากขึ้น ซึ่งการตัดสินใจนี้จะส่งผลต่อการปรับเปลี่ยนทั้งสถาปัตยกรรมทางเทคนิคและรูปแบบการไหลของข้อมูล (data-flow footprint) ของเว็บไซต์สมัยใหม่
