- สาเหตุที่เว็บไซต์ของ David Bushell แสดงผลพังมานานสำหรับผู้ใช้บางส่วน คือ CSS ที่ ส่วนขยายเบราว์เซอร์ Grammarly แอบฉีดเข้าไปในหน้าเว็บ
- ใน Firefox ส่วนขยาย Grammarly จะแทรก สไตล์ชีต จากทรัพยากรภายในส่วนขยายเอง ทำให้หาได้ยากผ่าน
StyleSheetList ของเว็บเพจ และยังหลบเลี่ยง Content Security Policy ได้ด้วย
- ปัญหาการชนกันเกิดจาก Grammarly กำหนด
--rem:16 แบบ global ไว้ที่ :root ขณะที่เว็บไซต์ก็ใช้ --rem ชื่อเดียวกันสำหรับคำนวณ ตัวพิมพ์แบบไหลลื่น
- ฝั่งเว็บไซต์กำหนด
--rem ไว้ใน cascade layer และตามกฎ CSS นั้นสไตล์ที่อยู่นอกเลเยอร์จะมีลำดับความสำคัญเหนือสไตล์ในเลเยอร์ ทำให้ค่าของ Grammarly สามารถไปทับการคำนวณได้
- วิธีแก้ชั่วคราวคือใช้ mutation observer กับ
!important พยุงไว้ แต่การรับมือสุดท้ายคือเปลี่ยนชื่อพร็อพเพอร์ตีเป็น --🤡 และถ้าส่วนขยายฉีดชื่อทั่วไปลงใน :root แบบ global ก็ชนกับเว็บเพจได้ง่าย
CSS ของ Grammarly ที่เข้ามาอยู่ในหน้าเว็บ
- ตลอดหลายเดือนมีรายงานประปรายว่าเลย์เอาต์ของเว็บไซต์เพี้ยนและขนาดแปลกไป พร้อมมีการส่งภาพหน้าจอมาให้ดู
- ผู้อ่านที่คุ้นเคยด้านเทคนิคชี้ว่า Grammarly browser extension คือสาเหตุหลัก และ David Bushell ก็ได้ติดตั้งบน Mullvad browser ที่อิง Firefox เพื่อยืนยันด้วยตนเอง
- เมื่อทำการติดตั้ง ส่วนขยายจะขอสิทธิ์ดังนี้
- เข้าถึงข้อมูลของทุกเว็บไซต์
- แสดงการแจ้งเตือน
- เข้าถึงแท็บของเบราว์เซอร์
- Grammarly ฉีด สไตล์ชีต ที่โหลดจากทรัพยากรภายในส่วนขยายลงในเว็บเพจ
- ส่วนขยายจะเพิ่ม custom element ชื่อ
<grammarly-desktop-integration> ลงในเอกสาร <html> ของทุกเว็บไซต์ แม้ผู้ใช้จะไม่ได้โต้ตอบอะไรเลยก็ตาม
ชื่อ --rem เพียงชื่อเดียวทำให้เลย์เอาต์พังได้อย่างไร
- ที่ท้ายสไตล์ชีตของ Grammarly มี CSS ดังนี้
:host,
:root {
--rem:16
}
- ส่วนอื่นของสไตล์ชีตเดียวกันก็ใช้
--rem เพื่อคำนวณขนาดตัวอักษรและความสูงบรรทัด
.kE2Bj {
font-size:calc(0.86px*(var(--rem) - 2));
line-height:calc(1.2868px*(var(--rem) - 2));
}
- ตัวเว็บไซต์เองก็ใช้ custom property ชื่อ
--rem สำหรับการทดลอง ตัวพิมพ์แบบไหลลื่น ของตัวเองเช่นกัน
@layer base {
:root {
--rem: 0.0625rem;
--fluid: calc((100vi - (400 * var(--rem))) / (1920 - 400));
--font-size-h1: clamp(
calc(31 * var(--rem)),
calc((31 * var(--rem)) + (80 - 31) * var(--fluid)),
calc(80 * var(--rem))
);
}
}
--rem ของเว็บไซต์ถูกกำหนดไว้ใน cascade layer และสไตล์ที่อยู่นอกเลเยอร์จะมีลำดับความสำคัญเหนือสไตล์ในเลเยอร์ โดยไม่ขึ้นกับ CSS specificity
- ลำดับของซอร์สก็มีผลด้วย ดังนั้น
--rem ของ Grammarly จึงอาจเป็นฝ่ายชนะ
- ผลคือสูตรคำนวณของเว็บไซต์พัง และเกิดปัญหาเลย์เอาต์
- ในช่วงแรกมีการรับมือโดยใช้ mutation observer ตรวจจับเว็บคอมโพเนนต์ที่ถูกเพิ่มเข้ามา แล้วเติมสไตล์
!important เข้าไป
- หลังระบุสาเหตุที่แท้จริงได้แล้ว จึงเปลี่ยนชื่อ custom property ของเว็บไซต์เป็น
--🤡
- ชื่อนี้เป็นชื่อ custom property ที่ใช้ได้ถูกต้องใน CSS
- ส่วน
--rem กลายเป็นชื่อที่เสี่ยงชนกัน เพราะ Grammarly ใช้แบบ global
- Grammarly สุ่มสร้างชื่อคลาส แต่กลับใช้ชื่อ custom property ทั่วไปอย่าง
--rem แบบ global กับ :root และยังฉีดโค้ดลงทุกเว็บเพจแม้ผู้ใช้จะไม่ได้ใช้งานส่วนขยายจริง ๆ
- มีการติดต่อทีมซัพพอร์ตของ Grammarly แล้ว แต่ตอนนี้ยังไม่สามารถติดต่อถึงผู้รับผิดชอบด้านเทคนิคที่เข้าใจปัญหาได้
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
กรณีที่เคยเจอจากปัญหาส่วนขยายจะแตกต่างออกไปเล็กน้อย เรากำลังเผยแพร่ส่วนขยายที่ช่วยให้ สลับพร็อกซีเซิร์ฟเวอร์ ได้ง่ายสำหรับการทดสอบตำแหน่งทางภูมิศาสตร์
เมื่อไม่กี่เดือนก่อนเราเดโมให้ลูกค้าดูได้แย่ที่สุดครั้งหนึ่ง เพราะผลิตภัณฑ์ดูเหมือนไม่ทำงานเลย หลังจากดีบักอยู่นาน ก็พบว่าการอัปเดตล่าสุดของ ส่วนขยาย 1Password ทำให้ส่วนขยายของเราพัง 1Password subscribe อีเวนต์การยืนยันตัวตนไว้แต่ไม่ return ทำให้หมดเวลา และ subscriber ของเราจึงไม่ถูกเรียก ส่วนขยายของเราสั่งเบราว์เซอร์ให้เปลี่ยนพร็อกซีเซิร์ฟเวอร์แล้วเตรียมพร้อมให้ข้อมูลรับรอง แต่ไม่มี request เข้ามา ทีมซัพพอร์ตของ 1Password ดีกว่า Grammarly แต่การโน้มน้าว PM ที่ไม่รู้ว่าเป็นใครผ่านทีมซัพพอร์ตให้จัดลำดับความสำคัญนั้นยาก
ต่อมาพบว่าส่วนขยายบางตัวที่จำเป็นสำหรับเว็บไซต์รัฐบาลรัสเซียก็มีปัญหาเดียวกัน
จากมุมมองของคนที่อยู่ฝั่งส่วนขยายมานานกว่า 10 ปี สุดท้าย Google มีความรับผิดชอบมากทีเดียว นอกเหนือจากประเด็นการเมืองเรื่องการเปลี่ยนแปลงตัวบล็อกโฆษณาแล้ว Manifest v3 ยังแย่กว่าที่คาดไว้มากในหลายด้าน
โดยรวมรู้สึกว่าคุณภาพของโค้ดเบส Chromium ลดลงจากเมื่อก่อนมาก
ถ้าจะ inject สคริปต์หรือสไตล์ลงในหน้าเว็บที่ไม่รู้จัก อย่างน้อยก็ควรแยก namespace ของตัวแปร ให้ชัดเจน
แต่ผู้สัมภาษณ์กลับพูดเหมือนปัด ๆ ว่าเรื่องแบบนั้นเครื่องมือสมัยนี้ทำให้หมดแล้ว และทุกคนก็ทำกันอยู่ ผมก็จำเป็นต้องเห็นด้วยอยู่บ้าง เพราะตอนนี้ไม่ได้ทำงานส่วนนั้นแล้วจึงไม่รู้จริง ๆ แต่กลายเป็นว่าไม่ได้มีทุกคนทำแบบนั้นเลย
เราแยกได้ชัดเจนว่าอะไรคือสิ่งที่เราแทรกเข้าไป และอะไรคือของเดิม พร้อมทั้งหลีกเลี่ยงการชนกันที่อาจเกิดขึ้นได้
เวลาหน้าจอถูกแชร์หรืออัดวิดีโอ แล้วเห็นผู้บุกรุกสีเขียวตัวนั้นติดตั้งเป็นค่าเริ่มต้นอยู่บนทุกเว็บไซต์ มันน่ากลัว ไม่ใช่แค่เรื่องรบกวนสายตา แต่ยังมีเรื่องความเป็นส่วนตัวและ attack vector ที่ชัดเจนตามมาด้วย
Chrome สามารถเปิดส่วนขยายเฉพาะเวลาจำเป็นได้ แต่ไม่เข้าใจว่าทำไมไม่มีใครทำแบบนั้น และก็สงสัยว่าทำไมค่าเริ่มต้นของทุกเบราว์เซอร์ถึงไม่เป็นแบบนั้น
เพื่อนร่วมงานบางคนไม่สบายใจกับความเป็นไปได้ที่ข้อมูลจะถูกส่งต่อไปยังบุคคลที่สาม จึงหยุดการประชุมจนกว่าจะปิดส่วนขยาย
ผมเป็นวิศวกรของ Grammarly Extension ก่อนอื่นต้องขออภัยจริง ๆ ที่ส่วนขยายของเราทำลาย ประสบการณ์ผู้ใช้ ของ dbushell.com และทำให้ผู้เขียนต้องเสียเวลาและความพยายามในการหาสาเหตุ
นี่ไม่ใช่สิ่งที่ตั้งใจ และเราก็ใช้หลายเทคนิคเพื่อไม่ให้เรื่องแบบนี้เกิดขึ้น แต่ยังไม่เพียงพอ และบทความก็ชี้ให้เห็นชัดเจนว่ายังมีพื้นที่ให้ปรับปรุง
ในฐานะการแก้ไขแบบเร่งด่วน เราได้เพิ่มข้อยกเว้นชั่วคราวให้ dbushell.com แล้ว ขณะเดียวกันก็กำลังทำการเปลี่ยนแปลงเพื่อรับประกัน การแยกสไตล์ อย่างเหมาะสม ปัญหาแบบนี้ไม่ควรเกิดขึ้นเลย
มีปัญหาคล้ายกันที่ Google Translate ทำให้เว็บแอปของผมพัง ผู้ใช้ใช้ Google Translate แล้วบ่นว่าแอปของผมเสีย แต่จริง ๆ แล้ว Google เป็นฝ่ายเปลี่ยนสถานะของแอปใน meta layer ที่สูงกว่า เป็นแนวปฏิบัติที่แย่มาก
กำลังพยายามตรวจจับ Google Translate แล้วแสดงคำเตือน
เช่น อาจต้องแปลประโยคอย่าง “คลิก[ที่นี่]เพื่อดูข้อมูลเพิ่มเติม” เมื่อนำไปเป็นภาษาอื่น อาจต้องย้ายลิงก์ไปท้ายประโยคให้เป็น “หากต้องการดูข้อมูลเพิ่มเติม [คลิกที่นี่]” การทำแบบนี้ต้อง จัดวางองค์ประกอบ DOM ใหม่ และนั่นอาจชนกับแอปแบบโต้ตอบได้
ทีม Google Translate มีหลายอย่างที่ทำได้เพื่อลดการแทรกแซง แต่ผมคิดว่ายากที่จะกำจัดให้หมดโดยไม่มี browser API ใหม่
ส่งต่อให้ทีมวิศวกรรมแล้ว
ที่ที่ผมทำงานอยู่ คนก็ไม่ทำแบบนั้นจนผมแทบคลั่ง แม้แต่ director ฝ่ายวิศวกรรมยังเพิ่มงานที่ใช้เวลาน้อยกว่าการจัดการ ticket เข้าเป็น ticket ของตัวเอง แต่ก็ถือเป็นสัญญาณที่ดีที่ได้ยินบ่อย ๆ ว่า “ผมไม่ได้สร้าง ticket เพื่อส่งข้อความ แต่ส่งข้อความหาคนนั้นโดยตรงตามวิธีของคุณแล้ว”
ในบริษัทมี ข้อผิดพลาดใน Sentry จำนวนมากที่เกิดจากส่วนขยายเบราว์เซอร์ทำอะไรแปลก ๆ
Google Translate ของ Chrome ก็ขึ้นชื่อว่าไปทำให้ไซต์ที่ใช้ React พัง
สุดท้ายกลายเป็นงานคัดแยกน่าเบื่อที่ต้องค่อย ๆ ตั้งให้มองข้ามปัญหาจากส่วนขยายใหม่ ๆ ทีละรายการ เราใช้การกรองฝั่งไคลเอนต์เพื่อลดปริมาณที่เก็บ โดยรวมแล้วมี noise มากกว่าแบ็กเอนด์ จึงต้องตั้ง threshold สูงกว่ามาก
ไม่แปลกที่ฝั่งฟรอนต์เอนด์จะมีข้อผิดพลาดมากกว่ามาก เพราะต้องรองรับ ความหลากหลายของไคลเอนต์ มากกว่าแบ็กเอนด์ทั่วไปอย่างมาก การสร้างเว็บแอปขนาดใหญ่ที่ทำงานได้ดีกับทุกคนอาจเป็นเรื่องยากมาก
สงสัยว่าถ้าจะฉีดตัวแปรสักตัวที่ทำให้เว็บพังได้หนักที่สุด ควรเป็นอะไร นึกออกแบบนี้:
--primary-color: transparent--serif: "Comic Sans MS"ควรรับมือกับส่วนขยายเบราว์เซอร์ที่เป็นปฏิปักษ์อย่างไร?
ตอนคิดเรื่องนี้ ผมลองเปิดหน้าไหนก็ได้ของ The Guardian ใน DevTools แล้วพบว่ามีใครบางคนแทรกสคริปต์และ iframe ที่ชี้ไปยัง twitter.com ไว้
ผมไม่ได้ชอบ Grammarly หรือโมเดลเทคโนโลยีของมัน แต่การโยนความมุ่งร้ายให้กับเรื่องที่อธิบายได้ด้วยความโง่เขลานั้นไม่ยุติธรรม
ผมไม่ได้ทำงานฟรอนต์เอนด์มานานแล้ว แต่ทั้งส่วนขยาย Grammarly และโค้ดของตัวเองไม่ควรใช้ ชื่อแอตทริบิวต์ที่แยก namespace กันหรือ?
ผมคิดว่าน่าจะใช้สิ่งนี้เพื่อ hijack ปลั๊กอินนั้นได้ อย่างน้อยก็น่าจะฉีดข้อความเข้าไปได้ และอาจถึงขั้น render ฟอร์มล็อกอินสวย ๆ เพื่อใช้ประโยชน์จากความเชื่อใจที่ผู้ใช้มีต่อส่วนขยายได้ด้วย
การฉีดองค์ประกอบเข้าไปในเอกสารที่คนอื่นควบคุมอยู่นั้นปลอดภัยจริงหรือ?
สิ่งที่ทำได้ก็แค่เลียนแบบ UI ของส่วนขยายภายในเว็บไซต์เอง ซึ่งไม่จำเป็นต้องฉีดอะไรเลย แค่ลอกดีไซน์มาก็พอ